-
.idea/workspace.xml 泄露文件信息,常见于用phpstorm写项目然后同步到github
image.png - 下载xdcms233.zip得到源码,有register.php login.php member.php 源码丢在最下面
主页是 index.html 通过按钮跳转到这几个php
这里看到xdebug,以为是phpstorm开了远程调试,因为题目说管理员开发完登陆界面就睡着了,2333
尝试打了payload,无果,参考文章
http://momomoxiaoxi.com/2017/09/18/WHCTF/
- 正常代码审计
- 大致逻辑是这样的,你可以注册一个用户,其中的code可以定义你的身份,如果你的code被pre_match处理后,能满足以下条件
$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
//$admin是每次随机生成的,碰撞的可能性是1/(35*35)
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0])
那你就是admin,不然你就是guest
image.pngimage.png
这题的正确做法是输入很长的code,让pre_match处理的时候出错,php进程崩掉,然后你后面的guest身份的插入语句就不会执行
赛后知道这个解法后,我真的是惊呆了。。。比赛时实在是搜不到能绕过pre_match的方法,因为这个给了头尾^$,以后搜不到还是得多看看php文档。。。
我比赛用的方法很匪夷所思,赛后跟出题人交流才知道原因,这是他的答复
image.png
导致数据库的user表和identities表清空的频率很快
- 可以反复注册同一个账户
-
登陆进去后,刚开始是guest,等一会刷新以下,你会发现你不是guest了,因为identities表清空了,自然就绕过了member.php的逻辑
image.png
同时也有条件竞争的解法
- 第一天出题者的数据库还是正常的,这个时候就只能注册不同用户,代码的逻辑是,注册时先将username插入user表,再将guest身份插入identities表,这之间有个间隙,而且由于pre_match()函数处理慢,这个间隙还是可利用的,code大概20个字符就能挺拖速度了,大概每秒注册10个用户,然后另一边同时用这10个用户去登陆,就能绕过guest了
-
第二天数据库异常,导致同一个用户能反复注册,那么只需要一个脚本疯狂地注册同一个用户,另一个脚本疯狂地用这个用户去登陆,就能利用这个间隙去绕过guest身份的插入
image.png
其实这里的条件竞争,还有另外一处
login.php里面会清理session
image.png
memeber.php的会检测session
image.png那么如果我们用脚本不断得注册并访问login.php,当member刚运行完此处
image.png
$_SESSION['is_logined'] 和 $_SESSION['is_guest'] 刚好在login.php那边被清除掉,接着member.php继续往下走,执行
image.png这样if里面的判断就是 0 || 0 ,自然能进入下面的else逻辑
绕过了guest,来到文件读取处
image.pngimage.png
进入下面else逻辑,开始读取文件,但is_file()判断是文件,就不让读取,不过好在readfile()函数支持php伪协议,成功绕过is_file() 读取config.php
payload:
http://123.206.120.239/member.php?file=php://filter/read=convert.base64-encode/resource=config.php
后端源码:
login.php
<?php
session_start();
include('config.php');
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
if (strlen($username) > 32 || strlen($password) > 32) {
die('Invalid input');
}
$sth = $pdo->prepare('SELECT password FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch()[0] !== $password) {
die('wrong password');
}
$_SESSION['username'] = $username;
unset($_SESSION['is_logined']);
unset($_SESSION['is_guest']);
#echo $username;
header("Location: member.php");
?>
register.php
<?php
include('config.php');
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
$code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
if (strlen($username) > 16 || strlen($username) > 16) {
die('Invalid input');
}
$sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch() !== false) {
die('username has been registered');
}
$sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
$sth->execute([':username' => $username, ':password' => $password]);
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0]) {
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
$sth->execute([':username' => $username, ':identity' => $matches[1]]);
} else {
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
$sth->execute([':username' => $username]);
}
echo '<script>alert("register success");location.href="./index.html"</script>';
member.php
<?php
include('config.php');
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
$code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
if (strlen($username) > 16 || strlen($username) > 16) {
die('Invalid input');
}
$sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch() !== false) {
die('username has been registered');
}
$sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
$sth->execute([':username' => $username, ':password' => $password]);
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0]) {
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
$sth->execute([':username' => $username, ':identity' => $matches[1]]);
} else {
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
$sth->execute([':username' => $username]);
}
echo '<script>alert("register success");location.href="./index.html"</script>';
网友评论