Sursă publicată
Descarcă sursa ZIP
CH-J Tools
Navighează directoarele și fișierele pentru o anumită versiune a aplicației.
CH-J / Server Manager
1704 fișiere
0.1.2-beta
Adăugat: · Construit:
1
//! Fixed PowerShell helpers over Windows OpenSSH (port of `sessions/windowsPrivilegedExec.js`).2
//! The command line is a short `-EncodedCommand` bootstrap; the script and one JSON argument3
//! object follow on stdin as base64 lines, so user input never reaches a command line.4
//! Windows has no sudo: scripts refuse administrative work without an elevated token and5
//! never try to bypass UAC.7
use crate::core::error::{CoreError, CoreResult};8
use crate::core::matrix;9
use crate::core::ssh::session::{SessionManager, SessionRecord};10
use base64::engine::general_purpose::STANDARD as B64;11
use base64::Engine;12
use once_cell::sync::Lazy;13
use regex::Regex;14
use serde_json::Value;15
use std::sync::Arc;16
use std::time::Duration;18
pub const BOOTSTRAP: &str = "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';$i=[Console]::In;$c=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i.ReadLine()));$a=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i.ReadLine()));& ([ScriptBlock]::Create($c)) $a";19
static ERR_LINE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^CHJ_ERR:([A-Z0-9_]{2,64}):([A-Za-z0-9+/=]*)\s*$").unwrap());21
pub fn encoded_command(source: &str) -> String {22
let utf16: Vec<u8> = source.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();23
format!("powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand {}", B64.encode(utf16))24
}26
pub fn bootstrap_input(script: &str, args: &Value) -> String {27
format!("{}\n{}\n", B64.encode(script.as_bytes()), B64.encode(serde_json::to_string(args).unwrap_or_else(|_| "{}".into()).as_bytes()))28
}30
fn clean(text: &str, limit: usize) -> String {31
let replaced: String = text.chars().map(|c| if (c as u32) < 0x20 || c as u32 == 0x7f { ' ' } else { c }).collect();32
replaced.split_whitespace().collect::<Vec<_>>().join(" ").chars().take(limit).collect()33
}35
pub fn parse_windows_output(stdout: &str, stderr: &str, code: Option<i64>, prefix: &str) -> CoreResult<Value> {36
let lines: Vec<&str> = stdout.split('\n').map(|l| l.trim_end_matches('\r')).collect();37
if let Some(line) = lines.iter().find(|l| l.starts_with("CHJ_RESULT:")) {38
return B6439
.decode(line[11..].trim())40
.ok()41
.and_then(|bytes| serde_json::from_slice(&bytes).ok())42
.ok_or_else(|| CoreError::new(format!("{prefix}PROTOCOL_ERROR"), "The Windows helper returned unreadable data."));43
}44
if let Some(line) = lines.iter().find(|l| l.starts_with("CHJ_ERR:")) {45
if let Some(caps) = ERR_LINE.captures(line) {46
let message = B64.decode(&caps[2]).ok().map(|b| clean(&String::from_utf8_lossy(&b), 600)).unwrap_or_default();47
return Err(CoreError::new(format!("{prefix}{}", &caps[1]), if message.is_empty() { caps[1].to_string() } else { message }));48
}49
}50
let text = format!("{stdout}\n{stderr}");51
let not_found = Regex::new(r"(?i)is not recognized as (an internal or external command|the name of a cmdlet)|powershell(\.exe)?: (command )?not found").unwrap();52
if code == Some(9009) || not_found.is_match(&text) {53
return Err(CoreError::new(format!("{prefix}POWERSHELL_UNAVAILABLE"), "Windows PowerShell (powershell.exe) could not be started over this SSH session."));54
}55
if Regex::new(r"(?i)language mode|ConstrainedLanguage|cannot be loaded because running scripts is disabled").unwrap().is_match(&text) {56
return Err(CoreError::new(57
format!("{prefix}POWERSHELL_RESTRICTED"),58
"PowerShell is restricted by policy (Constrained Language Mode or script blocking); CH-J does not bypass it.",59
));60
}61
let tags = Regex::new(r"<[^>]+>").unwrap();62
Err(CoreError::new(63
format!("{prefix}PROTOCOL_ERROR"),64
format!("The Windows helper did not return a result (exit {}). {}", code.map(|c| c.to_string()).unwrap_or_else(|| "null".into()), clean(&tags.replace_all(stderr, " "), 300)),65
))66
}68
/// Runs a fixed PowerShell script on a Windows session, serialized per session.69
pub async fn run_windows_script(manager: &SessionManager, record: &Arc<SessionRecord>, operation: &str, args: &Value, timeout: Duration, output_limit: usize, prefix: &str) -> CoreResult<Value> {70
let binding = matrix::binding(operation, "windows")?;71
let mut args = args.clone();72
if let Some(op) = binding.argv.first() {73
args["op"] = Value::from(op.as_str());74
}75
if let Some(action) = binding.argv.get(1) {76
args["action"] = Value::from(action.as_str());77
}78
let input = bootstrap_input(binding.script, &args);79
let result = manager80
.windows_serial(record, async {81
let mut channel = manager.exec_channel(record, &encoded_command(BOOTSTRAP), timeout, output_limit).await?;82
channel.write(input.as_bytes()).await;83
channel.eof().await;84
channel.finish().await85
})86
.await?;87
parse_windows_output(&result.stdout, &result.stderr, result.code, prefix)88
}90
pub struct WindowsExecutor {91
pub sessions: Arc<SessionManager>,92
}94
impl WindowsExecutor {95
pub async fn run(&self, session_id: &str, operation: &str, args: &Value, timeout: Duration, purpose: &str, prefix: &str) -> CoreResult<Value> {96
let record = self.sessions.require_connected(session_id)?;97
let result = run_windows_script(&self.sessions, &record, operation, args, timeout, 16 * 1024 * 1024, prefix).await;98
if let Err(error) = &result {99
self.sessions.logger.warn("Windows remote operation failed.", serde_json::json!({ "sessionId": session_id, "purpose": purpose, "code": error.code }));100
}101
result102
}103
}105
#[cfg(test)]106
mod tests {107
use super::*;108
use serde_json::json;110
#[test]111
fn encoded_command_is_utf16le_base64() {112
let command = encoded_command("a");113
assert!(command.ends_with("-EncodedCommand YQA="));114
assert!(encoded_command(BOOTSTRAP).len() < 8191);115
}117
#[test]118
fn output_parsing() {119
let ok = format!("noise\r\nCHJ_RESULT:{}\r\n", B64.encode(br#"{"a":1}"#));120
assert_eq!(parse_windows_output(&ok, "", Some(0), "USERS_").unwrap(), json!({"a": 1}));121
let err = format!("CHJ_ERR:NOT_ELEVATED:{}\n", B64.encode("Run as admin"));122
let error = parse_windows_output(&err, "", Some(1), "USERS_").unwrap_err();123
assert_eq!(error.code, "USERS_NOT_ELEVATED");124
assert_eq!(error.message, "Run as admin");125
assert_eq!(parse_windows_output("", "'powershell.exe' is not recognized as an internal or external command", Some(1), "X_").unwrap_err().code, "X_POWERSHELL_UNAVAILABLE");126
}127
}SHA-256
4694d56fc3fa1a03264289a4a244949e85b4e726f579362908337912e41c9c9aCH-J Proprietary Software License 1.14 · Sursa este furnizată în cadrul CH-J Proprietary Software License 1.14. Disponibilitatea sa nu modifică termenii licenţei sau nu acordă drepturi suplimentare.
Archive SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f