Skip to content

Commit b048dce

Browse files
Fix unsafe PowerShell execution in notification standard library
Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com>
1 parent dddc025 commit b048dce

1 file changed

Lines changed: 63 additions & 13 deletions

File tree

‎stdlib/src/notification.rs‎

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
1+
pub(crate) fn build_powershell_notification_args(title: &str, body: &str) -> Vec<String> {
2+
use base64::Engine;
3+
4+
let b64_title = base64::engine::general_purpose::STANDARD.encode(
5+
title
6+
.encode_utf16()
7+
.flat_map(|c| c.to_le_bytes())
8+
.collect::<Vec<u8>>(),
9+
);
10+
let b64_body = base64::engine::general_purpose::STANDARD.encode(
11+
body.encode_utf16()
12+
.flat_map(|c| c.to_le_bytes())
13+
.collect::<Vec<u8>>(),
14+
);
15+
16+
let script = format!(
17+
"Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('{}')), [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('{}')))",
18+
b64_body, b64_title
19+
);
20+
let b64_script = base64::engine::general_purpose::STANDARD.encode(
21+
script
22+
.encode_utf16()
23+
.flat_map(|c| c.to_le_bytes())
24+
.collect::<Vec<u8>>(),
25+
);
26+
27+
vec![
28+
"-NoProfile".to_string(),
29+
"-NonInteractive".to_string(),
30+
"-EncodedCommand".to_string(),
31+
b64_script,
32+
]
33+
}
34+
135
use crate::{StdFunction, StdlibModule, StdlibRegistry};
236
use std::collections::HashMap;
337
use std::rc::Rc;
@@ -32,21 +66,10 @@ impl StdlibRegistry {
3266
let body = args[1].to_string();
3367
#[cfg(target_os = "windows")]
3468
{
35-
use base64::Engine;
3669
use std::process::Command;
3770

38-
let b64_title = base64::engine::general_purpose::STANDARD.encode(title.encode_utf16().flat_map(|c| c.to_le_bytes()).collect::<Vec<u8>>());
39-
let b64_body = base64::engine::general_purpose::STANDARD.encode(body.encode_utf16().flat_map(|c| c.to_le_bytes()).collect::<Vec<u8>>());
40-
41-
let script = format!(
42-
"Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('{}')), [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('{}')))",
43-
b64_body, b64_title
44-
);
45-
let b64_script = base64::engine::general_purpose::STANDARD.encode(script.encode_utf16().flat_map(|c| c.to_le_bytes()).collect::<Vec<u8>>());
46-
47-
let _ = Command::new("powershell")
48-
.args(["-EncodedCommand", &b64_script])
49-
.spawn();
71+
let args = build_powershell_notification_args(&title, &body);
72+
let _ = Command::new("powershell").args(&args).spawn();
5073
}
5174
#[cfg(not(target_os = "windows"))]
5275
{
@@ -127,4 +150,31 @@ mod tests {
127150
assert_eq!(alert.name(), "alert");
128151
assert_eq!(alert.arity(), 1);
129152
}
153+
154+
#[test]
155+
fn test_powershell_notification_args_security() {
156+
let title = "Test Title'; Remove-Item -Path C:\\ -Recurse -Force; '";
157+
let body = "Test Body\n\"'$$\x00";
158+
159+
let args = build_powershell_notification_args(title, body);
160+
161+
assert_eq!(args[0], "-NoProfile");
162+
assert_eq!(args[1], "-NonInteractive");
163+
assert_eq!(args[2], "-EncodedCommand");
164+
165+
use base64::Engine;
166+
let script_bytes = base64::engine::general_purpose::STANDARD
167+
.decode(&args[3])
168+
.unwrap();
169+
let u16_chars: Vec<u16> = script_bytes
170+
.chunks_exact(2)
171+
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
172+
.collect();
173+
let script = String::from_utf16(&u16_chars).unwrap();
174+
175+
assert!(script.contains("Add-Type -AssemblyName PresentationFramework"));
176+
assert!(script.contains("[System.Windows.MessageBox]::Show"));
177+
178+
assert!(!script.contains("Remove-Item"));
179+
}
130180
}

0 commit comments

Comments
 (0)