phaser 0.7.8

High-performance attack surface mapper and vulnerability scanner
use crate::{
    modules::{HttpModule, Module, ModuleName, ModuleVersion},
    report::{Finding, ModuleResult, Severity},
    Error,
};
use async_trait::async_trait;
use regex::Regex;
use reqwest::Client;

pub struct GitConfigDisclosure {
    git_config_regex: Regex,
}

impl GitConfigDisclosure {
    pub fn new() -> Self {
        GitConfigDisclosure {
            git_config_regex: Regex::new(r#"\[branch "[^"]*"\]"#)
                .expect("compiling http/git_config_disclosure regexp"),
        }
    }

    async fn is_git_config_file(&self, content: String) -> Result<bool, Error> {
        let git_config_regex = self.git_config_regex.clone();
        let res = tokio::task::spawn_blocking(move || git_config_regex.is_match(&content)).await?;

        Ok(res)
    }
}

impl Module for GitConfigDisclosure {
    fn name(&self) -> ModuleName {
        ModuleName::HttpGitConfigDisclosure
    }

    fn description(&self) -> String {
        String::from("Check for .git/config file disclosure")
    }

    fn version(&self) -> ModuleVersion {
        ModuleVersion(1, 0, 0)
    }

    fn is_aggressive(&self) -> bool {
        false
    }

    fn severity(&self) -> Severity {
        Severity::High
    }
}

#[async_trait]
impl HttpModule for GitConfigDisclosure {
    async fn scan(&self, http_client: &Client, endpoint: &str) -> Result<Option<Finding>, Error> {
        let url = format!("{}/.git/config", &endpoint);
        let res = http_client.get(&url).send().await?;

        if !res.status().is_success() {
            return Ok(None);
        }

        let body = res.text().await?;
        if self.is_git_config_file(body).await? {
            return Ok(Some(Finding {
                module: self.name(),
                module_version: self.version(),
                severity: self.severity(),
                result: ModuleResult::Url(url),
            }));
        }

        Ok(None)
    }
}

#[cfg(test)]
mod tests {
    use super::GitConfigDisclosure;

    #[tokio::test]
    async fn is_git_directory() {
        let module = GitConfigDisclosure::new();

        let body = r#"[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[remote "origin"]
        url = git@github.com:skerkour/black-hat-rust.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master"#;

        let body2 = "test test test test tes  <tle>Index of kerkour.fr</title> test";

        assert_eq!(
            true,
            module.is_git_config_file(body.to_string()).await.unwrap()
        );
        assert_eq!(
            false,
            module.is_git_config_file(body2.to_string()).await.unwrap()
        );
    }
}