<commit_before>//
// OpenRASP 官方插件只是抛砖引玉，具体检测能力请根据业务来定制
//

'use strict'
var plugin  = new RASP('offical')

const clean = {
    action:     'ignore',
    message:    '无风险',
    confidence: 0
}

var forcefulBrowsing = {
    dotFiles: /\.(7z|tar|gz|bz2|xz|rar|zip|sql|db)$/,
    nonUserDirectory: /^\/(proc|sys|root)/,
    unwantedFilenames: [
        // user files
        '.DS_Store',
        'id_rsa', 'id_rsa.pub', 'known_hosts', 'authorized_keys', 
        '.bash_history', '.csh_history', '.zsh_history', '.mysql_history',

        // project files
        '.htaccess', '.user.ini',

        'web.config', 'web.xml', 'build.property.xml', 'bower.json',
        'Gemfile', 'Gemfile.lock',
        '.gitignore',
        'error_log', 'error.log', 'nohup.out',
    ],
    unwantedDirectory: [
        '/',
        '/home',
        '/var/log',
        '/proc',
        '/sys'
    ],
    absolutePaths: [
        '/etc/shadow',
        '/etc/hosts',
        '/etc/apache2/apache2.conf',
        '/root/.bash_history',
        '/root/.bash_profile',
    ]
}

var scriptFileRegex = /\.(jspx?|php[345]?|phtml)\.?$/i
var ntfsRegex       = /::\$(DATA|INDEX)$/i // 其他的stream都没啥用

String.prototype.replaceAll = function(token, tokenValue) {
    var index  = 0;
    var string = this;
    
    do {
        string = string.replace(token, tokenValue);
    } while((index = string.indexOf(token, index + 1)) > -1);

    return string
}

function canonicalPath (path) {
    return path.replaceAll('/./', '/').replaceAll('//', '/').replaceAll('//', '/')
}

function basename (path) {
    var idx = path.lastIndexOf('/')
    return path.substr(idx + 1)
}

function ip2long(ipstr) {
    var items  = ipstr.split('.')
    var result = 0

    for (var i = 0; i < items.length; i ++) {
        result = (result << 8) + parseInt(items[i], 10)
    }
    return result >>> 0
    return ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0;
}

// 主要用于识别webshell里的文件管理器
// 通常程序不会主动列目录或者查看敏感目录，e.g /home /etc /var/log 等等
// 
// 若有特例可调整
// 可结合业务定制: e.g 不能超出应用根目录
plugin.register('directory', function (params, context) {
    var realpath = params.realpath

    for (var i = 0; i < forcefulBrowsing.unwantedDirectory.length; i ++) {
        if (realpath == forcefulBrowsing.unwantedDirectory[i]) {
            return {
                action:     'block',
                message:    '疑似WebShell文件管理器 - 读取敏感根目录',
                confidence: 100
            }
        }
    }

    return clean
})

plugin.register('readFile', function (params, context) {

    // 算法1: 检查是否为成功的目录扫描
    var filename_1 = basename(context.url)
    var filename_2 = basename(params.realpath)

    if (filename_1 == filename_2) {
        var matched = false

        // 尝试下载压缩包、SQL文件等等
        if (forcefulBrowsing.dotFiles.test(filename_1)) {
            matched = true
        } else {
            // 尝试访问敏感文件
            for (var i = 0; i < forcefulBrowsing.unwantedFilenames; i ++) {
                if (forcefulBrowsing.unwantedFilenames[i] == filename_1) {
                    matched = true
                }
            }
        }

        if (matched) {
            return {
                action:     'block',
                message:    '尝试下载敏感文件(' + context.method.toUpperCase() + ' 方式): ' + params.realpath,

                // 如果是HEAD方式下载敏感文件，100% 扫描器攻击
                confidence: context.method == 'head' ? 100 : 90
            }
        }
    }

    // 算法2: 如果使用绝对路径访问敏感文件，判定为 webshell
    if (params.realpath == params.path) {
        for (var j = 0; j < forcefulBrowsing.absolutePaths.length; j ++) {
            if (forcefulBrowsing.absolutePaths[j] == params.realpath) {
                return {
                    action:     'block',
                    message:    'WebShell/文件管理器 - 尝试读取系统文件: ' + params.realpath,
                    confidence: 90
                }
            }
        }
    }

    // 算法3: 检查文件遍历，看是否超出web目录范围
    var path        = canonicalPath(params.path)
    var appBasePath = context.appBasePath

    if (params.realpath.indexOf(appBasePath) == -1 && (path.indexOf('/../') !== -1 || path.indexOf('\\..\\') !== -1)) {
        return {
            action:     'block',
            message:    '目录遍历攻击，跳出web目录范围 (' + appBasePath + ')',
            confidence: 90
        }
    }

    return clean
})

plugin.register('ssrf', function (params, context) {
    var hostname = params.hostname
    var reason   = false

    // 检查常见探测域名
    if (hostname.endsWith('.xip.io') || hostname.endsWith('.burpcollaborator.net')) {
        reason = '访问已知的内网探测域名'    
    } 
    // 检测AWS私有地址，如有误报可注释掉
    else if (1 && hostname == '169.254.169.254') {        
        reason = '尝试读取 AWS metadata'
    }
    // 检查混淆: 
    // http://2130706433
    // 
    // 以下混淆方式没有检测
    // http://0x7f.0x0.0x0.0x1
    else if (! isNaN(hostname)) {
        reason = '尝试使用纯数字IP'
    }<commit_msg>Let us release that way<commit_after>//
// OpenRASP 官方插件只是抛砖引玉，具体检测能力请根据业务来定制
//

'use strict'
var plugin  = new RASP('offical')

const clean = {
    action:     'ignore',
    message:    '无风险',
    confidence: 0
}

var forcefulBrowsing = {
    dotFiles: /\.(7z|tar|gz|bz2|xz|rar|zip|sql|db)$/,
    nonUserDirectory: /^\/(proc|sys|root)/,
    unwantedFilenames: [
        // user files
        '.DS_Store',
        'id_rsa', 'id_rsa.pub', 'known_hosts', 'authorized_keys', 
        '.bash_history', '.csh_history', '.zsh_history', '.mysql_history',

        // project files
        '.htaccess', '.user.ini',

        'web.config', 'web.xml', 'build.property.xml', 'bower.json',
        'Gemfile', 'Gemfile.lock',
        '.gitignore',
        'error_log', 'error.log', 'nohup.out',
    ],
    unwantedDirectory: [
        '/',
        '/home',
        '/var/log',
        '/proc',
        '/sys'
    ],
    absolutePaths: [
        '/etc/shadow',
        '/etc/hosts',
        '/etc/apache2/apache2.conf',
        '/root/.bash_history',
        '/root/.bash_profile',
    ]
}

var scriptFileRegex = /\.(jspx?|php[345]?|phtml)\.?$/i
var ntfsRegex       = /::\$(DATA|INDEX)$/i // 其他的stream都没啥用

String.prototype.replaceAll = function(token, tokenValue) {
    var index  = 0;
    var string = this;
    
    do {
        string = string.replace(token, tokenValue);
    } while((index = string.indexOf(token, index + 1)) > -1);

    return string
}

function canonicalPath (path) {
    return path.replaceAll('/./', '/').replaceAll('//', '/').replaceAll('//', '/')
}

function basename (path) {
    var idx = path.lastIndexOf('/')
    return path.substr(idx + 1)
}

function ip2long(ipstr) {
    var items  = ipstr.split('.')
    var result = 0

    for (var i = 0; i < items.length; i ++) {
        result = (result << 8) + parseInt(items[i], 10)
    }
    return result >>> 0
    return ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0;
}

// 主要用于识别webshell里的文件管理器
// 通常程序不会主动列目录或者查看敏感目录，e.g /home /etc /var/log 等等
// 
// 若有特例可调整
// 可结合业务定制: e.g 不能超出应用根目录
plugin.register('directory', function (params, context) {
    var realpath = params.realpath

    for (var i = 0; i < forcefulBrowsing.unwantedDirectory.length; i ++) {
        if (realpath == forcefulBrowsing.unwantedDirectory[i]) {
            return {
                action:     'block',
                message:    '疑似WebShell文件管理器 - 读取敏感根目录',
                confidence: 100
            }
        }
    }

    return clean
})

plugin.register('readFile', function (params, context) {

    // 算法1: 检查是否为成功的目录扫描
    var filename_1 = basename(context.url)
    var filename_2 = basename(params.realpath)

    if (filename_1 == filename_2) {
        var matched = false

        // 尝试下载压缩包、SQL文件等等
        if (forcefulBrowsing.dotFiles.test(filename_1)) {
            matched = true
        } else {
            // 尝试访问敏感文件
            for (var i = 0; i < forcefulBrowsing.unwantedFilenames; i ++) {
                if (forcefulBrowsing.unwantedFilenames[i] == filename_1) {
                    matched = true
                }
            }
        }

        if (matched) {
            return {
                action:     'block',
                message:    '尝试下载敏感文件(' + context.method.toUpperCase() + ' 方式): ' + params.realpath,

                // 如果是HEAD方式下载敏感文件，100% 扫描器攻击
                confidence: context.method == 'head' ? 100 : 90
            }
        }
    }

    // 算法2: 如果使用绝对路径访问敏感文件，判定为 webshell
    if (params.realpath == params.path) {
        for (var j = 0; j < forcefulBrowsing.absolutePaths.length; j ++) {
            if (forcefulBrowsing.absolutePaths[j] == params.realpath) {
                return {
                    action:     'block',
                    message:    'WebShell/文件管理器 - 尝试读取系统文件: ' + params.realpath,
                    confidence: 90
                }
            }
        }
    }

    // 算法3: 检查文件遍历，看是否超出web目录范围
    var path        = canonicalPath(params.path)
    var appBasePath = context.appBasePath

    if (params.realpath.indexOf(appBasePath) == -1 && (path.indexOf('/../') !== -1 || path.indexOf('\\..\\') !== -1)) {
        return {
            action:     'block',
            message:    '目录遍历攻击，跳出web目录范围 (' + appBasePath + ')',
            confidence: 90
        }
    }

    return clean
})

plugin.register('ssrf', function (params, context) {
    var hostname = params.hostname
    var reason   = false

    // 检查常见探测域名
    if (hostname.endsWith('.xip.io') || hostname.endsWith('.burpcollaborator.net')) {
        reason = '访问已知的内网探测域名'    
    } 
    // 检测AWS私有地址，如有误报可注释掉
    else if (1 && hostname == '169.254.169.254') {        
        reason = '尝试读取 AWS metadata'
    }
    // 检查混淆: 
    // http://2130706433
    // 
    // 以下混淆方式没有检测
    // http://0x7f.0x0.0x0.0x1
    // http://0x7f.0.0.0
    // http://0x7f001
    else if (! isNaN(hostname)) {
        reason = '尝试使用纯数字IP'
    }
{'fraction_non_alphanumeric': 0.15594285135272065, 'fraction_numerical': 0.018238929982774345, 'mean_word_length': 1.8345778288340036, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4523044', 'n_tokens_mistral': 3824, 'n_tokens_neox': 3810, 'n_words': 628}