security: fix audit points 5-9 + optimize echo_push/info endpoints
- Point 5 (SSRF): Add URL validation for WELIST_SERVER_URL (src/validation.rs) - Point 6 (DB Access): Add DB path validation, symlink check, WAL mode (open_db) - Point 8 (HTTPS): Extract nginx config, add deployment checklist, bind warnings - Point 9 (Input Validation): Add NETWORKS check (404 for unknown), txid 64-hex validation - Optimize echo_push: parse transactions outside DB lock, batch duplicate check, N+1 xpub lookup eliminated via HashSet cache - Optimize echo_info: derive BIP32 address outside DB lock, minimize lock duration - Fix echo_stats SQL injection via parameter binding + add idx_stats_chain index - New regression tests: ssrf_tests, db_path_validation, input_validation_tests
This commit is contained in:
@@ -20,6 +20,8 @@ use std::str;
|
||||
use std::{thread, time::Duration};
|
||||
use zmq::{Context, DEALER, DONTWAIT, Socket};
|
||||
|
||||
use bal_server::db::open_db;
|
||||
use bal_server::validation::is_valid_welist_url;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::PKey;
|
||||
@@ -74,7 +76,7 @@ struct NetworkParams {
|
||||
cookie_file: String,
|
||||
rpc_user: String,
|
||||
rpc_pass: String,
|
||||
zmq_listener:String
|
||||
zmq_listener: String,
|
||||
}
|
||||
fn get_network_params(cfg: &MyConfig, network: Network) -> &NetworkParams {
|
||||
match network {
|
||||
@@ -235,7 +237,13 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
|
||||
debug!("best block hash: {}", bcinfo.best_block_hash);
|
||||
|
||||
let average_time = bcinfo.median_time;
|
||||
let db = sqlite::open(&cfg.db_file).unwrap();
|
||||
let db = match open_db(&cfg.db_file) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("Fatal: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
info!("db open {}", &cfg.db_file);
|
||||
|
||||
let sqlquery = "SELECT * FROM tbl_tx WHERE network = :network AND status = :status AND ( locktime < :bestblock_height OR locktime > :locktime_threshold AND locktime < :bestblock_time);";
|
||||
@@ -321,7 +329,11 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
|
||||
}
|
||||
async fn calculate_stats(db: &Connection, chain: String) -> Result<(), reqwest::Error> {
|
||||
// Validate chain to prevent SQL injection via environment variable tampering
|
||||
if !chain.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') || chain.is_empty() {
|
||||
if !chain
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
|
||||
|| chain.is_empty()
|
||||
{
|
||||
error!("Invalid chain name: {chain}");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -403,7 +415,13 @@ async fn send_stats_report(
|
||||
debug!("sending report to welist");
|
||||
let welist_url = env::var("WELIST_SERVER_URL")
|
||||
.unwrap_or("https://welist.bitcoin-after.life".to_string());
|
||||
|
||||
if !is_valid_welist_url(&welist_url) {
|
||||
warn!(
|
||||
"Invalid or unsafe WELIST_SERVER_URL: {}. Skipping stats report.",
|
||||
welist_url
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let client = rClient::new();
|
||||
let url = format!("{}/ping", welist_url);
|
||||
debug!("welist url: {}", url);
|
||||
@@ -480,14 +498,15 @@ fn parse_env_netconfig(cfg_lock: &mut MyConfig, chain: &str) -> NetworkParams {
|
||||
}
|
||||
match env::var(format!("BAL_PUSHER_{}_PORT", chain.to_uppercase())) {
|
||||
Ok(value) => match value.parse::<u64>() {
|
||||
Ok(value) => {
|
||||
match u16::try_from(value) {
|
||||
Ok(port) => cfg.port = port,
|
||||
Err(e) => {
|
||||
error!("Port value {} exceeds u16 range for chain {}: {}", value, chain, e);
|
||||
}
|
||||
Ok(value) => match u16::try_from(value) {
|
||||
Ok(port) => cfg.port = port,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Port value {} exceeds u16 range for chain {}: {}",
|
||||
value, chain, e
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {}
|
||||
},
|
||||
Err(_) => {}
|
||||
@@ -522,10 +541,13 @@ fn parse_env_netconfig(cfg_lock: &mut MyConfig, chain: &str) -> NetworkParams {
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
println!("{}",format!("BAL_PUSHER_{}_ZMQ_HASHBLOCK", chain.to_uppercase()));
|
||||
println!(
|
||||
"{}",
|
||||
format!("BAL_PUSHER_{}_ZMQ_HASHBLOCK", chain.to_uppercase())
|
||||
);
|
||||
match env::var(format!("BAL_PUSHER_{}_ZMQ_HASHBLOCK", chain.to_uppercase())) {
|
||||
Ok(value) => {
|
||||
println!("value:{}",value);
|
||||
println!("value:{}", value);
|
||||
cfg.zmq_listener = value;
|
||||
}
|
||||
Err(_) => {}
|
||||
@@ -640,7 +662,7 @@ async fn main() -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
match socket.set_subscribe(b"") {
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("ZMQ subscribe failed: {}, exiting", e);
|
||||
return Ok(());
|
||||
|
||||
Reference in New Issue
Block a user