diff options
author | Vika Shleina <vika@fireburn.ru> | 2021-07-21 06:07:35 +0300 |
---|---|---|
committer | Vika Shleina <vika@fireburn.ru> | 2021-07-21 06:07:35 +0300 |
commit | c98e370326102dac0c7c16c9b556da018b41803b (patch) | |
tree | ca9a4024e18b663ba148133c74731752ff1e1114 | |
parent | 36811f1aaa96cb58ab5de5d59e28375f28414b74 (diff) | |
download | kittybox-c98e370326102dac0c7c16c9b556da018b41803b.tar.zst |
Fixed security hole where other people could delete YOUR posts. Yes, yours. You're welcome.
-rw-r--r-- | src/lib.rs | 42 | ||||
-rw-r--r-- | src/micropub/post.rs | 25 |
2 files changed, 67 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs index 0ea7860..d4a63d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,48 @@ mod tests { } #[async_std::test] + async fn test_no_deletion_of_others_posts() { + let _m = mock("GET", "/") + .with_status(200) + .with_header("Content-Type", "application/json") + .with_body(r#"{"me": "https://fireburn.ru", "client_id": "https://quill.p3k.io/", "scope": "create update media"}"#) + .create(); + + let (db, app, _r) = create_app().await; + + let mut response = post_json( + &app, + json!({ + "type": ["h-entry"], + "properties": { + "content": ["This is content!"] + } + }), + ) + .await; + println!( + "{:#}", + response.body_json::<serde_json::Value>().await.unwrap() + ); + assert!(response.status() == 201 || response.status() == 202); + let uid = response.header("Location").unwrap().last().to_string(); + drop(_m); + let _m = mock("GET", "/") + .with_status(200) + .with_header("Content-Type", "application/json") + .with_body(r#"{"me": "https://aaronparecki.com/", "client_id": "https://quill.p3k.io/", "scope": "create update delete media"}"#) + .create(); + + let mut response = app.post("/micropub") + .header("Authorization", "Bearer awoo") + .header("Content-Type", "application/json") + .body(json!({ "action": "delete", "url": uid })) + .send().await.unwrap(); + println!("{}", response.body_string().await.unwrap()); + assert_eq!(response.status(), 403); + } + + #[async_std::test] async fn test_no_posting_to_others_websites() { let _m = mock("GET", "/") .with_status(200) diff --git a/src/micropub/post.rs b/src/micropub/post.rs index eaa603a..95b4dd0 100644 --- a/src/micropub/post.rs +++ b/src/micropub/post.rs @@ -511,6 +511,17 @@ async fn process_json<S: Storage>( "You need a `delete` scope to delete posts." ); } + // This special scope is not available through a token endpoint, since the + // authorization endpoint is supposed to reject any auth request trying to get this + // scope. It is intended for TRUSTED external services that need to modify the + // database while ignoring any access controls + if (url::Url::parse(url)?.origin().ascii_serialization() + "/") != user.me.as_str() && !user.check_scope("kittybox_internal:do_what_thou_wilt") { + return error_json!( + 403, + "forbidden", + "You're not allowed to delete someone else's posts." + ) + } if let Err(error) = req.state().storage.delete_post(&url).await { return Ok(error.into()); } @@ -524,6 +535,13 @@ async fn process_json<S: Storage>( "You need an `update` scope to update posts." ); } + if (url::Url::parse(url)?.origin().ascii_serialization() + "/") != user.me.as_str() && !user.check_scope("kittybox_internal:do_what_thou_wilt") { + return error_json!( + 403, + "forbidden", + "You're not allowed to delete someone else's posts." + ) + } if let Err(error) = req.state().storage.update_post(&url, body.clone()).await { Ok(error.into()) } else { @@ -591,6 +609,13 @@ async fn process_form<S: Storage>( } match form.iter().find(|(k, _)| k == "url") { Some((_, url)) => { + if (url::Url::parse(url)?.origin().ascii_serialization() + "/") != user.me.as_str() && !user.check_scope("kittybox_internal:do_what_thou_wilt") { + return error_json!( + 403, + "forbidden", + "You're not allowed to delete someone else's posts." + ) + } if let Err(error) = req.state().storage.delete_post(&url).await { return error_json!(500, "database_error", error); } |