security: path traversal via unvalidated context/context_type/context_id in filesystem storage #41
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_biz#41
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Three new file-based storage paths join user-controlled route parameters directly into filesystem paths without any validation:
If
context,context_type, orcontext_idcontain..or/, an attacker can read or write files outsidedb_root. The filename sanitizer insave_uploadonly applies to the filename, not the directory components.Fix
Validate each path component matches
[a-zA-Z0-9_-]+before joining, or canonicalize and assert the resolved path starts withdb_root.Affected code
crates/hero_biz_ui/src/services/mod.rs—save_chat_message,save_upload,link_dirFound in
PR #21 review
Implementation Spec — Issue #41
Objective
Eliminate path traversal vulnerabilities in three
Storemethods that join user-controlled strings directly into filesystem paths without validation. An attacker who can send arbitrary values forcontext,context_type, orcontext_id(all sourced from URL route parameters) can escapedb_rootand read or write arbitrary files on the server.Affected Code
crates/hero_biz_ui/src/services/mod.rs—save_chat_message,save_upload,link_dirsave_chat_message.join(&msg.context_type).join(&msg.context_sid)save_upload.join(context_type).join(context_id)link_dir.join(context)delete_person_company_linklink_dir(context)+.join(link_sid)(also unvalidated)Fix Approach
Add a module-scope helper:
Call it before every
PathBuf::jointhat uses user-supplied input.Files to Modify
crates/hero_biz_ui/src/services/mod.rs— all changesImplementation Steps
Step 1: Add
validate_path_componenthelperAdd the function at module scope in
services/mod.rs, beforeimpl Store.Step 2: Update
save_chat_messageBefore the
let dir = ...line, add:Step 3: Update
save_uploadBefore the
let dir = ...line, add:Step 4: Update
link_dirsignatureChange return type from
PathBuftoResult<PathBuf>and add validation:Step 5–6: Update
link_dircallersload_all_person_company_links:let dir = self.link_dir(context)?;save_person_company_link:let dir = self.link_dir(context)?;Step 7: Update
delete_person_company_linkStep 8: Add unit tests
Add
#[cfg(test)]module at the bottom ofservices/mod.rscovering: valid components,..,/, empty string, null byte, and percent-encoded sequences.Acceptance Criteria
cargo build -p hero_biz_uipasses with no errorscargo test -p hero_biz_uipasses with all new tests green..,/, spaces, or non-[a-zA-Z0-9_-]characters are rejected before any filesystem accessNotes
link_sidindelete_person_company_linkis also user-supplied and must be validated (Step 7 covers it — not in original issue scope but same vulnerability class)%is rejected)Test Results
Build: pass
Tests: 7 passed, 0 failed
Test suite:
hero_biz_uiunit testsAll 7 unit tests passed:
parser::tests::test_name_fixservices::tests::dot_dot_is_rejectedservices::tests::empty_string_is_rejectedservices::tests::path_traversal_sequences_rejectedservices::tests::null_byte_is_rejectedservices::tests::slash_is_rejectedservices::tests::valid_components_passDoc-tests and binary tests: 0 tests (none defined).
Branch:
development_casperImplementation Complete
Changes Made
All changes are in
crates/hero_biz_ui/src/services/mod.rs:validate_path_component(s: &str) -> Result<()>helper that rejects any string not matching[a-zA-Z0-9_-]+, using the existingHeroBizError::Validationerror variant.save_chat_message: validatescontext_typeandcontext_sidbefore joining into path.save_upload: validatescontext_typeandcontext_idbefore joining into path.link_dir: signature changed from-> PathBufto-> Result<PathBuf>; validatescontextbefore joining.load_all_person_company_links,save_person_company_link: updated to propagatelink_dir's newResultreturn with?.delete_person_company_link: validateslink_sid(also user-supplied, same vulnerability class) before joining into path; updatedlink_dircall...,/, empty string, null byte, and percent-encoded/spaced traversal sequences.Test Results
Build: pass
Tests: 7 passed, 0 failed
Fixed and committed in
0a24041. All validation checks in place.