Ruby: Add rb/tainted-format-string query#8272
Conversation
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
2 similar comments
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
0aabff3 to
8f11802
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
8f11802 to
aa5cd34
Compare
aa5cd34 to
aae109e
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
aae109e to
d34838b
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
d34838b to
492c75a
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, a method such as References
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, string interpolation should be used exclusively: class UsersController < ActionController::Base
def index
puts "Unauthorised access attempt by #{params[:user]}: #{request.ip}"
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, string interpolation should be used exclusively: class UsersController < ActionController::Base
def index
puts "Unauthorised access attempt by #{params[:user]}: #{request.ip}"
end
endReferences
|
alexrford
left a comment
There was a problem hiding this comment.
Looks good, only minor comments.
| DataFlow::Node getFormatString() { result = this.getArgument(0) } | ||
|
|
||
| /** | ||
| * Gets then `n`th formatted argument of this call. |
There was a problem hiding this comment.
We should probably either change this to be 0-indexed rather than 1-indexed, or note in the QLDoc that it's 1-indexed.
| * A call to `IO#printf`. | ||
| */ | ||
| class IOPrintfCall extends PrintfStyleCall { | ||
| IOPrintfCall() { this = API::getTopLevelMember("IO").getInstance().getAMethodCall("printf") } |
There was a problem hiding this comment.
| IOPrintfCall() { this = API::getTopLevelMember("IO").getInstance().getAMethodCall("printf") } | |
| IOPrintfCall() { this.getReceiver() instanceof IOInstance and this.getMethodName() = "printf" } |
with an import of codeql.ruby.frameworks.Files::IO.
This will catch cases like:
file = File.open "foo.txt", "a"
file.printf(params[:format], arg) # BAD
where the IO instance is created without new - this case is already caught by the UnknownMethodCall catch-all as it stands, but that might not be the case if/when UnknownMethodCall is removed.
| // Kernel#printf supports two signatures: | ||
| // printf(io, string, ...) | ||
| // printf(string, ...) | ||
| override DataFlow::Node getFormatString() { result = this.getArgument([0, 1]) } |
There was a problem hiding this comment.
As it stands we can end up with FPs of the form:
printf("hello %s %s", params[:format], "arg2")
where params[:format] is currently interpreted as a format string due to appearing as arg1, but is only a format argument.
I think we could avoid this by checking the type of arg0, e.g. as a rough heuristic:
| override DataFlow::Node getFormatString() { result = this.getArgument([0, 1]) } | |
| override DataFlow::Node getFormatString() { | |
| if this.getArgument(0).asExpr() instanceof StringLiteralCfgNode | |
| then result = this.getArgument(0) | |
| else result = this.getArgument([0, 1]) | |
| } |
In this way, we avoid FPs where arg0 is trivially a format string, but we err on the side of over-reporting when we're unsure of the type of arg0.
There was a problem hiding this comment.
Good point! I've made this change, though instead of instanceof StringLiteralCfgNode I've used .getConstantValue().isString(_), to take advantage of local flow, e.g. in
fmt = "hello %s %s"
printf(fmt, params[:format], "arg2")This relies on IO objects not having a string constant value, but I think that's a safe bet.
It will now identify cases like this:
file = File.open "foo.txt", "a"
file.printf(params[:format], arg)
Kernel#printf supports two call signatures:
printf(String, *args)
printf(IO, String, *args)
We want to identify the String argument, which is the format string.
Previously we would return the 0th and 1st arguments, which gives some
FPs when the 1st arg is not a format string.
We now try to rule out the trivial case by checking if arg 0 has a
string value, and then assuming it is the format string. Otherwise we
fall back to returning both arguments.
This still has some false positive potential, but less than previously.
9f4e6b7 to
5a6da82
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-134/TaintedFormatString.qhelpUse of externally-controlled format stringMethods like RecommendationEither sanitize the input before including it in the format string, or use a ExampleThe following program snippet logs information about an unauthorized access attempt. The log message includes the user name, and the user's IP address is passed as an additional argument to class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
end
endHowever, if a malicious user provides a format specified such as Instead, the user name should be included using the class UsersController < ActionController::Base
def index
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
end
endAlternatively, string interpolation should be used exclusively: class UsersController < ActionController::Base
def index
puts "Unauthorised access attempt by #{params[:user]}: #{request.ip}"
end
endReferences
|
| */ | ||
|
|
||
| import javascript | ||
| import semmle.javascript.security.dataflow.DOM |
There was a problem hiding this comment.
I think this import semmle.javascript.security.dataflow.DOM is redundant.
But that's fine.
(ql/redundant-import anyone?)
There was a problem hiding this comment.
A query for redundant imports would be great! Even better would be a compiler warning delivered via LSP.
There was a problem hiding this comment.
I don't think it belongs as a compiler warning.
In the above case both of the imports eventually import each other, so deleting any one of them will solve it.
And do you want a compiler warning on both for that? I think that feels wrong, and so does the arbitrary choice of warning on the second.
And sometimes (redundant) imports and their order can be important (I'm not sure why, but I think the CPP team can tell stories).
In QL-for-QL: arbitary choices and lingering warnings are fine, so I think it should be a query.
(But I want to figure out how we'll get support for parameterized modules before I start more QL-for-QL query writing).
This shares as much as possible with the JS version.