mirror of
https://github.com/unraid/api.git
synced 2025-12-31 05:29:48 -06:00
130 lines
3.6 KiB
Plaintext
130 lines
3.6 KiB
Plaintext
/**
|
|
* @name Path Traversal Vulnerability
|
|
* @description User-controlled inputs used in file operations may allow for path traversal attacks.
|
|
* @kind path-problem
|
|
* @problem.severity error
|
|
* @precision high
|
|
* @id js/path-traversal
|
|
* @tags security
|
|
* external/cwe/cwe-22
|
|
*/
|
|
|
|
import javascript
|
|
import DataFlow::PathGraph
|
|
|
|
/**
|
|
* Identifies sources of user-controlled input
|
|
*/
|
|
class UserInput extends DataFlow::Node {
|
|
UserInput() {
|
|
// HTTP request parameters
|
|
exists(DataFlow::ParameterNode param |
|
|
param.getName().regexpMatch("(?i).*(req|request|param|query|body|user|input).*") and
|
|
this = param
|
|
)
|
|
or
|
|
// Access to common request properties
|
|
exists(DataFlow::PropRead prop |
|
|
(
|
|
prop.getPropertyName() = "query" or
|
|
prop.getPropertyName() = "body" or
|
|
prop.getPropertyName() = "params" or
|
|
prop.getPropertyName() = "files"
|
|
) and
|
|
this = prop
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Identifies fs module imports
|
|
*/
|
|
class FileSystemAccess extends DataFlow::CallNode {
|
|
FileSystemAccess() {
|
|
// Node.js fs module functions
|
|
exists(string name |
|
|
name = this.getCalleeName() and
|
|
(
|
|
name = "readFile" or
|
|
name = "readFileSync" or
|
|
name = "writeFile" or
|
|
name = "writeFileSync" or
|
|
name = "appendFile" or
|
|
name = "appendFileSync" or
|
|
name = "createReadStream" or
|
|
name = "createWriteStream" or
|
|
name = "openSync" or
|
|
name = "open"
|
|
)
|
|
)
|
|
or
|
|
// File system operations via require('fs')
|
|
exists(DataFlow::SourceNode fsModule, string methodName |
|
|
(fsModule.getAPropertyRead("promises") or fsModule).flowsTo(this.getReceiver()) and
|
|
methodName = this.getMethodName() and
|
|
(
|
|
methodName = "readFile" or
|
|
methodName = "writeFile" or
|
|
methodName = "appendFile" or
|
|
methodName = "readdir" or
|
|
methodName = "stat"
|
|
)
|
|
)
|
|
}
|
|
|
|
DataFlow::Node getPathArgument() {
|
|
result = this.getArgument(0)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Identifies sanitization of file paths
|
|
*/
|
|
predicate isPathSanitized(DataFlow::Node node) {
|
|
// Check for path normalization or validation
|
|
exists(DataFlow::CallNode call |
|
|
(
|
|
call.getCalleeName() = "resolve" or
|
|
call.getCalleeName() = "normalize" or
|
|
call.getCalleeName() = "isAbsolute" or
|
|
call.getCalleeName() = "relative" or
|
|
call.getCalleeName().regexpMatch("(?i).*(sanitize|validate|check).*path.*")
|
|
) and
|
|
call.flowsTo(node)
|
|
)
|
|
or
|
|
// Check for path traversal mitigation patterns
|
|
exists(DataFlow::CallNode call |
|
|
call.getCalleeName() = "replace" and
|
|
exists(StringLiteral regex |
|
|
regex = call.getArgument(0).(DataFlow::RegExpCreationNode).getSource().getAChildExpr() and
|
|
regex.getValue().regexpMatch("(\\.\\./|\\.\\.\\\\)")
|
|
) and
|
|
call.flowsTo(node)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Configuration for tracking flow from user input to file system operations
|
|
*/
|
|
class PathTraversalConfig extends TaintTracking::Configuration {
|
|
PathTraversalConfig() { this = "PathTraversalConfig" }
|
|
|
|
override predicate isSource(DataFlow::Node source) {
|
|
source instanceof UserInput
|
|
}
|
|
|
|
override predicate isSink(DataFlow::Node sink) {
|
|
exists(FileSystemAccess fileAccess |
|
|
sink = fileAccess.getPathArgument()
|
|
)
|
|
}
|
|
|
|
override predicate isSanitizer(DataFlow::Node node) {
|
|
isPathSanitized(node)
|
|
}
|
|
}
|
|
|
|
from PathTraversalConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
|
where config.hasFlowPath(source, sink)
|
|
select sink.getNode(), source, sink, "File system operation depends on a user-controlled value $@.", source.getNode(), "user input" |