Initial PoC of spoofing Android OAuth
This commit is contained in:
parent
ba89b76332
commit
383d2789ce
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -482,6 +482,17 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.10"
|
version = "0.4.10"
|
||||||
@ -712,6 +723,7 @@ name = "libreddit"
|
|||||||
version = "0.30.1"
|
version = "0.30.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
|
"base64",
|
||||||
"brotli",
|
"brotli",
|
||||||
"build_html",
|
"build_html",
|
||||||
"cached",
|
"cached",
|
||||||
@ -735,6 +747,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1590,6 +1603,15 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -30,6 +30,8 @@ toml = "0.7.4"
|
|||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
serde_yaml = "0.9.16"
|
serde_yaml = "0.9.16"
|
||||||
build_html = "2.2.0"
|
build_html = "2.2.0"
|
||||||
|
uuid = { version = "1.3.3", features = ["v4"] }
|
||||||
|
base64 = "0.21.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lipsum = "0.9.0"
|
lipsum = "0.9.0"
|
||||||
|
@ -7,18 +7,22 @@ use libflate::gzip;
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use percent_encoding::{percent_encode, CONTROLS};
|
use percent_encoding::{percent_encode, CONTROLS};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::sync::RwLock;
|
||||||
use std::{io, result::Result};
|
use std::{io, result::Result};
|
||||||
|
|
||||||
use crate::dbg_msg;
|
use crate::dbg_msg;
|
||||||
|
use crate::oauth::{Oauth, USER_AGENT};
|
||||||
use crate::server::RequestExt;
|
use crate::server::RequestExt;
|
||||||
|
|
||||||
const REDDIT_URL_BASE: &str = "https://www.reddit.com";
|
const REDDIT_URL_BASE: &str = "https://oauth.reddit.com";
|
||||||
|
|
||||||
static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| {
|
pub(crate) static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| {
|
||||||
let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build();
|
let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build();
|
||||||
client::Client::builder().build(https)
|
client::Client::builder().build(https)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub(crate) static OAUTH_CLIENT: Lazy<RwLock<Oauth>> = Lazy::new(|| RwLock::new(Oauth::new()));
|
||||||
|
|
||||||
/// Gets the canonical path for a resource on Reddit. This is accomplished by
|
/// Gets the canonical path for a resource on Reddit. This is accomplished by
|
||||||
/// making a `HEAD` request to Reddit at the path given in `path`.
|
/// making a `HEAD` request to Reddit at the path given in `path`.
|
||||||
///
|
///
|
||||||
@ -136,9 +140,9 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
|||||||
let builder = Request::builder()
|
let builder = Request::builder()
|
||||||
.method(method)
|
.method(method)
|
||||||
.uri(&url)
|
.uri(&url)
|
||||||
.header("User-Agent", format!("web:libreddit:{}", env!("CARGO_PKG_VERSION")))
|
.header("User-Agent", USER_AGENT)
|
||||||
.header("Host", "www.reddit.com")
|
.header("Host", "oauth.reddit.com")
|
||||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
|
.header("Authorization", &format!("Bearer {}", OAUTH_CLIENT.read().unwrap().token))
|
||||||
.header("Accept-Encoding", if method == Method::GET { "gzip" } else { "identity" })
|
.header("Accept-Encoding", if method == Method::GET { "gzip" } else { "identity" })
|
||||||
.header("Accept-Language", "en-US,en;q=0.5")
|
.header("Accept-Language", "en-US,en;q=0.5")
|
||||||
.header("Connection", "keep-alive")
|
.header("Connection", "keep-alive")
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
mod config;
|
mod config;
|
||||||
mod duplicates;
|
mod duplicates;
|
||||||
mod instance_info;
|
mod instance_info;
|
||||||
|
mod oauth;
|
||||||
mod post;
|
mod post;
|
||||||
mod search;
|
mod search;
|
||||||
mod settings;
|
mod settings;
|
||||||
@ -25,6 +26,8 @@ use once_cell::sync::Lazy;
|
|||||||
use server::RequestExt;
|
use server::RequestExt;
|
||||||
use utils::{error, redirect, ThemeAssets};
|
use utils::{error, redirect, ThemeAssets};
|
||||||
|
|
||||||
|
use crate::client::OAUTH_CLIENT;
|
||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
// Create Services
|
// Create Services
|
||||||
@ -167,6 +170,11 @@ async fn main() {
|
|||||||
Lazy::force(&config::CONFIG);
|
Lazy::force(&config::CONFIG);
|
||||||
Lazy::force(&instance_info::INSTANCE_INFO);
|
Lazy::force(&instance_info::INSTANCE_INFO);
|
||||||
|
|
||||||
|
// Force login of Oauth client
|
||||||
|
#[allow(clippy::await_holding_lock)]
|
||||||
|
// We don't care if we are awaiting a lock here - it's just locked once at init.
|
||||||
|
OAUTH_CLIENT.write().unwrap().login().await;
|
||||||
|
|
||||||
// Define default headers (added to all responses)
|
// Define default headers (added to all responses)
|
||||||
app.default_headers = headers! {
|
app.default_headers = headers! {
|
||||||
"Referrer-Policy" => "no-referrer",
|
"Referrer-Policy" => "no-referrer",
|
||||||
|
53
src/oauth.rs
Normal file
53
src/oauth.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::client::CLIENT;
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
use hyper::{client, Body, Method, Request};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
static REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg";
|
||||||
|
|
||||||
|
static AUTH_ENDPOINT: &str = "https://accounts.reddit.com";
|
||||||
|
pub(crate) static USER_AGENT: &str = "Reddit/Version 2023.21.0/Build 956283/Android 13";
|
||||||
|
|
||||||
|
pub(crate) struct Oauth {
|
||||||
|
// Currently unused, may be necessary if we decide to support GQL in the future
|
||||||
|
pub(crate) headers_map: HashMap<String, String>,
|
||||||
|
pub(crate) token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oauth {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let uuid = uuid::Uuid::new_v4().to_string();
|
||||||
|
Oauth {
|
||||||
|
headers_map: HashMap::from([
|
||||||
|
("Client-Vendor-Id".into(), uuid.clone()),
|
||||||
|
("X-Reddit-Device-Id".into(), uuid),
|
||||||
|
("User-Agent".into(), USER_AGENT.to_string()),
|
||||||
|
]),
|
||||||
|
token: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn login(&mut self) -> Option<()> {
|
||||||
|
let url = format!("{}/api/access_token", AUTH_ENDPOINT);
|
||||||
|
let mut builder = Request::builder().method(Method::POST).uri(&url);
|
||||||
|
for (key, value) in self.headers_map.iter() {
|
||||||
|
builder = builder.header(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth = general_purpose::STANDARD.encode(format!("{REDDIT_ANDROID_OAUTH_CLIENT_ID}:"));
|
||||||
|
builder = builder.header("Authorization", format!("Basic {auth}"));
|
||||||
|
let json = json!({
|
||||||
|
"scopes": ["*","email","pii"]
|
||||||
|
});
|
||||||
|
let body = Body::from(json.to_string());
|
||||||
|
let request = builder.body(body).unwrap();
|
||||||
|
let client: client::Client<_, hyper::Body> = CLIENT.clone();
|
||||||
|
let resp = client.request(request).await.ok()?;
|
||||||
|
let body_bytes = hyper::body::to_bytes(resp.into_body()).await.ok()?;
|
||||||
|
let json: serde_json::Value = serde_json::from_slice(&body_bytes).ok()?;
|
||||||
|
self.token = json.get("access_token")?.as_str()?.to_string();
|
||||||
|
self.headers_map.insert("Authorization".to_owned(), format!("Bearer {}", self.token));
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user