• 6.12ctf练习


    [西湖论剑 2022]Node Magical Login

    源码在这里:GitHub - CTF-Archives/2022-xhlj-web-node_magical_login: A web challenge in 2022 西湖论剑大赛打开

    打开环境是个登录框,先进行了扫描和抓包都没有看见什么有价值的东西,看源码

    大致连接到存在flag1和flag2

    1. function LoginController(req,res) {
    2. try {
    3. const username = req.body.username
    4. const password = req.body.password
    5. if (username !== "admin" || password !== Math.random().toString()) {
    6. res.status(401).type("text/html").send("Login Failed")
    7. } else {
    8. res.cookie("user",SECRET_COOKIE)
    9. res.redirect("/flag1")
    10. }
    11. } catch (__) {}
    12. }
    1. function Flag1Controller(req,res){
    2. try {
    3. if(req.cookies.user === SECRET_COOKIE){
    4. res.setHeader("This_Is_The_Flag1",flag1.toString().trim())
    5. res.setHeader("This_Is_The_Flag2",flag2.toString().trim())
    6. res.status(200).type("text/html").send("Login success. Welcome,admin!")
    7. }
    8. if(req.cookies.user === "admin") {
    9. res.setHeader("This_Is_The_Flag1", flag1.toString().trim())
    10. res.status(200).type("text/html").send("You Got One Part Of Flag! Try To Get Another Part of Flag!")
    11. }else{
    12. res.status(401).type("text/html").send("Unauthorized")
    13. }
    14. }catch (__) {}
    15. }

    从这两部分代码中能够知道登录成功的条件是"admin/生成随机数转换为的字符串",爆破随机数这个不太可能,应该就要从别的地方入手

    由于每次调用 Math.random() 都会产生一个新的随机数,密码验证几乎不可能成功,除非每次用户输入的密码恰好是 Math.random().toString() 的结果(这在实际情况下是不可能的)

    看登录成功之后会生成一个名为user的cookie,值为SECRET_COOKIE,再重定向到flag1

    在下一部分的代码中,当从客户端发送的cookie中user=admin就会返回第一部分的flag

    所以直接抓包flag1修改cookie

    接下来获取flag2

    1. function CheckController(req,res) {
    2. let checkcode = req.body.checkcode?req.body.checkcode:1234;
    3. console.log(req.body)
    4. if(checkcode.length === 16){
    5. try{
    6. checkcode = checkcode.toLowerCase()
    7. if(checkcode !== "aGr5AtSp55dRacer"){
    8. res.status(403).json({"msg":"Invalid Checkcode1:" + checkcode})
    9. }
    10. }catch (__) {}
    11. res.status(200).type("text/html").json({"msg":"You Got Another Part Of Flag: " + flag2.toString().trim()})
    12. }else{
    13. res.status(403).type("text/html").json({"msg":"Invalid Checkcode2:" + checkcode})
    14. }
    15. }

    路由

    1. app.post("/getflag2",(req,res)=> {
    2. controller.CheckController(req,res)
    3. })

    请求体中的checkcode长度要为16,并且在toLowerCase()转换为小写后字符串=="aGr5AtSp55dRacer"

    在 Web 应用的安全性验证中,toLowerCase() 的验证逻辑如果没有针对输入的数据格式做严格的检查,可能会被利用 JSON 数据结构进行绕过

    利用JSON数组来绕过

    解释一下这个数组的构造思路

    JSON数组的结构

    {"数组名称":["a","b","c"]}

    "aGr5AtSp55dRacer"在数组中为一个值,所以要满足长度为16,还需要添加除了预期字符串之外的一系列数字,验证代码简单地提取 checkcode 字段,而没有检查其类型,就直接使用整个数组,导致 toLowerCase() 等字符串操作被绕过

    {"checkcode":["aGr5AtSp55dRacer",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
    

    记得把类型改为json,否则会报错

    [NCTF 2018]Easy_Audit

    源码

    1. highlight_file(__FILE__);
    2. error_reporting(0);
    3. if($_REQUEST){
    4. foreach ($_REQUEST as $key => $value) {
    5. if(preg_match('/[a-zA-Z]/i', $value)) die('waf..');
    6. }
    7. }
    8. if($_SERVER){
    9. if(preg_match('/yulige|flag|nctf/i', $_SERVER['QUERY_STRING'])) die('waf..');
    10. }
    11. if(isset($_GET['yulige'])){
    12. if(!(substr($_GET['yulige'], 32) === md5($_GET['yulige']))){ //日爆md5!!!!!!
    13. die('waf..');
    14. }else{
    15. if(preg_match('/nctfisfun$/', $_GET['nctf']) && $_GET['nctf'] !== 'nctfisfun'){
    16. $getflag = file_get_contents($_GET['flag']);
    17. }
    18. if(isset($getflag) && $getflag === 'ccc_liubi'){
    19. include 'flag.php';
    20. echo $flag;
    21. }else die('waf..');
    22. }
    23. }
    24. ?>

    借这题也是比较清楚的认识了php的超级全局变量,现在开始一层一层分析源码

    第一层
    1. if($_REQUEST){
    2. foreach ($_REQUEST as $key => $value) {
    3. if(preg_match('/[a-zA-Z]/i', $value)) die('waf..');
    4. }
    5. }

    foreach,一个遍历数组并把值赋给value,但是这个$_REQUEST是什么?

    $_REQUEST 是 PHP 中的一个超级全局变量数组,用于收集通过 GET、POST 和 COOKIE 发送到当前脚本的请求数据。它整合了 $_GET$_POST$_COOKIE 三个数组中的数据。

    同时$_REQUEST还具有一个特性,当同时通过POST和GET传参时,如果POST和GET中含有相同的变量时,只会获取POST传参的变量值

    那么第一层就利用POST传参进行变量覆盖,且post传参的值中不能出现字母,绕过第一层的判断

    第二层
    1. if($_SERVER){
    2. if(preg_match('/yulige|flag|nctf/i', $_SERVER['QUERY_STRING'])) die('waf..');
    3. }

    $_SERVER也是一个超级全局变量, $_SERVER['QUERY_STRING']指获取get传参的变量和值

    1. http://xxx.com?a=123
    2. 那么 $_SERVER['QUERY_STRING']就获取a=123

    所以就限制了get传参中不能出现被正则过滤的变量,编码绕过就行

    第三层
    1. if(isset($_GET['yulige'])){
    2. if(!(substr($_GET['yulige'], 32) === md5($_GET['yulige']))){ //日爆md5!!!!!!
    3. die('waf..');
    4. }else{
    5. if(preg_match('/nctfisfun$/', $_GET['nctf']) && $_GET['nctf'] !== 'nctfisfun'){
    6. $getflag = file_get_contents($_GET['flag']);
    7. }
    8. if(isset($getflag) && $getflag === 'ccc_liubi'){
    9. include 'flag.php';
    10. echo $flag;
    11. }else die('waf..');
    12. }
    13. }
    一.md5强比较

    GET传参yulige,截取前32位和md5加密后的值相同,其实就是一个md5的强比较,传入数组为空即成立

    二.正则表达式的绕过

    "$"表示正则匹配的位置,这里放在了末尾说明在字符串的末尾进行正则匹配,所以只要在"nctfisfun"前面加数字或者字母就行,只要末尾能匹配到要求字符串就行

    三.php伪协议

    file_get_contents读取$flag,赋值给getflag==='ccc_liubi',用data伪协议写入就行

    所以完整的GET传参payload

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

    POST传参

    yulige=1&nctf=1&flag=1

  • 相关阅读:
    三十分钟带你玩转Python异常处理
    archlinux ssdm的使用
    [{data:{data:[{}]},{data:{data:[{}]}] JS解构赋值拿到内层的data数据
    JSONP实现跨域请求,cors解决跨域以及nginx反向代理实现跨域
    【计算机考研408强化-操作系统】3. 内存管理
    栈和队列(8.4)
    【无标题】QCC 308x 518x 517x增加usb voice 32k采样率
    程序产生自我意识,创造人工生命
    C++的学习
    【10套模拟】【5】
  • 原文地址:https://blog.csdn.net/weixin_74020633/article/details/139626323