Merge remote-tracking branch 'origin/main' into jorgectf/python/insecure-cookie

This commit is contained in:
jorgectf
2021-11-05 20:14:06 +01:00
27 changed files with 284 additions and 353 deletions

View File

@@ -1,13 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>about General Class-Level Information</p>
<!--TOC-->
</overview>
</qhelp>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>about Architecture</p>
<!--TOC-->
</overview>
</qhelp>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>about best practices</p>
<!--TOC-->
</overview>
</qhelp>

View File

@@ -1,42 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
This metric measures the number of lines of text that have been added, deleted
or modified in files below this location in the tree.
</p>
<p>
Code churn is known to be a good (if not the best) predictor of defects in a
code component (see e.g. [Nagappan] or [Khoshgoftaar]). The intuition is that
files, packages or projects that have experienced a disproportionately high
amount of churn for the amount of code involved may have been harder to write,
and are thus likely to contain more bugs.
</p>
</overview>
<recommendation>
<p>
It is a fact of life that some code is going to be changed more than the rest,
and little can be done to change this. However, bearing in mind code churn's
effectiveness as a defect predictor, code that has been repeatedly changed
should be subjected to vigorous testing and code review.
</p>
</recommendation>
<references>
<li>
N. Nagappan et al. <em>Change Bursts as Defect Predictors</em>. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010.
</li>
<li>
T. M. Khoshgoftaar and R. M. Szabo. <em>Improving code churn predictions during the system test and maintenance phases</em>. In ICSM '94, 1994, pp. 58-67.
</li>
</references>
</qhelp>

View File

@@ -1,6 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="HChurn.qhelp" />
</qhelp>

View File

@@ -1,6 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="HChurn.qhelp" />
</qhelp>

View File

@@ -1,48 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
This metric measures the number of different authors (by examining the
version control history)
for files below this location in the tree. (This is a better version
of the metric that counts the number of different authors using Javadoc
tags.)
</p>
<p>
Files that have been changed by a large number of different authors are
by definition the product of many minds. New authors working on a file
may be less familiar with the design and implementation of the code than
the original authors, which can be a potential source of bugs. Furthermore,
code that has been worked on by many people, if not carefully maintained,
often ends up lacking conceptual integrity. For both of these reasons, any
code that has been worked on by an unusually high number of different people
merits careful inspection in code reviews.
</p>
</overview>
<recommendation>
<p>
There is clearly no way to reduce the number of authors that have worked
on a file - it is impossible to rewrite history. However, files highlighted
by this metric should be given special attention in a code review, and may
ultimately be good candidates for refactoring/rewriting by an individual,
experienced developer.
</p>
</recommendation>
<references>
<li>
F. P. Brooks Jr. <em>The Mythical Man-Month</em>, Chapter 4. Addison-Wesley, 1974.
</li>
</references>
</qhelp>

View File

@@ -1,30 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
This metric measures the total number of file-level changes made to files
below this location in the tree. For an individual file, it measures the
number of commits that have affected that file. For a directory of files, it
measures the sum of the file-level changes for each of the files in the
directory.
</p>
<p>
For example, suppose we have a directory containing two files, A and B. If the
number of file-level changes to A is <code>100</code>, and the number of
file-level changes to B is <code>80</code>, then the total number of
file-level changes to the directory is <code>180</code>. Note that this is
likely to be different (in some cases very different) from the number of
commits that affected any file in the directory, since more than one file can
be changed by a single commit. (Note what would happen if we performed
<code>80</code> commits on A and B, followed by another <code>20</code>
commits on A alone - the total number of file-level changes would be
<code>180</code>, but the number of commits involved would be
<code>100</code>.)
</p>
</overview>
</qhelp>

View File

@@ -1,51 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
This metric measures the average number of co-committed files for the files
below this location in the tree.
</p>
<p>
A co-committed file is one that is committed at the same time as a given file.
For instance, if you commit files A, B and C together, then B and C would be
the co-committed files of A for that commit. The value of the metric for an
individual file is the average number of such co-committed files over all
commits. The value of the metric for a directory is the aggregation of these
averages - for instance, if we are using <code>max</code> as our aggregation
function, the value would be the maximum of the average number of co-commits
over all files in the directory.
</p>
<p>
An unusually high value for this metric may indicate that the file in question
is too tightly-coupled to other files, and it is difficult to change it in
isolation. Alternatively, it may just be an indication that you commit lots of
unrelated changes at the same time.
</p>
</overview>
<recommendation>
<p>
Examine the file in question to see what the problem is.
</p>
<ul>
<li>
If the file is too tightly coupled, it will have high values for its afferent
and/or efferent coupling metrics, and you should apply the advice given there.
</li>
<li>
If the file is not tightly coupled, but you find that you are committing lots
of unrelated changes at the same time, then you may want to revisit your commit
practices.
</li>
</ul>
</recommendation>
</qhelp>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
This metric measures the number of file re-commits that have occurred below
this location in the tree. A re-commit is taken to mean a commit to a file
that was touched less than five days ago.
</p>
<p>
In a system that is being developed using a controlled change process (where
changes are not committed until they are in some sense 'complete'), re-commits
can be (but are not always) an indication that an initial change was not
successful and had to be revisited within a short time period. The intuition
is that the original change may have been difficult to get right, and hence
the code in the file may be more than usually defect-prone. The concept is
somewhat similar to that of 'change bursts', as described in [Nagappan].
</p>
</overview>
<recommendation>
<p>
High numbers of re-commits can be addressed on two levels: preventative and
corrective.
</p>
<ul>
<li>
On the preventative side, a high number of re-commits may be an indication
that your code review process needs an overhaul.
</li>
<li>
On the corrective side, code that has experienced a high number of re-commits
should be vigorously code reviewed and tested.
</li>
</ul>
</recommendation>
<references>
<li>
N. Nagappan et al. <em>Change Bursts as Defect Predictors</em>. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010.
</li>
</references>
</qhelp>

View File

@@ -1,63 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
This metric measures the number of recent changes to files that have occurred
below this location in the tree. A recent change is taken to mean a change
that has occurred in the last <code>180</code> days.
</p>
<p>
All code that has changed a great deal may be more than usually prone to
defects, but this is particularly true of code that has been changing
dramatically in the recent past, because it has not yet had a chance to be
properly field-tested in order to iron out the bugs.
</p>
</overview>
<recommendation>
<p>
There is more than one reason why a file may have been changing a lot
recently:
</p>
<ul>
<li>
The file may be part of a new subsystem that is being written. New code is
always going to change a lot in a short period of time, but it is important
to ensure that it is properly code reviewed and unit tested before integrating
it into a working product.
</li>
<li>
The file may be being heavily refactored. Large refactorings are sometimes
essential, but they are also quite risky. You should write proper regression
tests before starting on a major refactoring, and check that they still pass
once you're done.
</li>
<li>
The same bit of code may be being changed repeatedly because it is difficult
to get right. Aside from vigorous code reviewing and testing, it may be a good
idea to rethink the system design - if something is that hard
to get right (and it's not an inherently difficult concept), you might be making life unnecessarily hard for yourself and
risking introducing insidious defects.
</li>
</ul>
</recommendation>
<references>
<li>
N. Nagappan et al. <em>Change Bursts as Defect Predictors</em>. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010.
</li>
</references>
</qhelp>

View File

@@ -158,8 +158,10 @@ Python built-in support
Flask, Web framework
Tornado, Web framework
Twisted, Web framework
PyYAML, Serialization
starlette, Asynchronous Server Gateway Interface (ASGI)
dill, Serialization
PyYAML, Serialization
ruamel.yaml, Serialization
simplejson, Serialization
ujson, Serialization
fabric, Utility library

View File

@@ -9,7 +9,7 @@ cn.hutool.core.codec,,,1,,,,,,,,,,,,,,,,,,,,,1,
com.esotericsoftware.kryo.io,,,1,,,,,,,,,,,,,,,,,,,,,1,
com.esotericsoftware.kryo5.io,,,1,,,,,,,,,,,,,,,,,,,,,1,
com.fasterxml.jackson.core,,,1,,,,,,,,,,,,,,,,,,,,,1,
com.fasterxml.jackson.databind,,,5,,,,,,,,,,,,,,,,,,,,,5,
com.fasterxml.jackson.databind,,,6,,,,,,,,,,,,,,,,,,,,,6,
com.google.common.base,,,85,,,,,,,,,,,,,,,,,,,,,62,23
com.google.common.cache,,,17,,,,,,,,,,,,,,,,,,,,,,17
com.google.common.collect,,,553,,,,,,,,,,,,,,,,,,,,,2,551
@@ -90,3 +90,12 @@ org.springframework.web.util,,,163,,,,,,,,,,,,,,,,,,,,,138,25
org.xml.sax,,,1,,,,,,,,,,,,,,,,,,,,,1,
org.xmlpull.v1,,3,,,,,,,,,,,,,,,,,,,,,3,,
play.mvc,,4,,,,,,,,,,,,,,,,,,,,,4,,
ratpack.core.form,,,3,,,,,,,,,,,,,,,,,,,,,3,
ratpack.core.handling,,6,4,,,,,,,,,,,,,,,,,,,,6,4,
ratpack.core.http,,10,10,,,,,,,,,,,,,,,,,,,,10,10,
ratpack.exec,,,26,,,,,,,,,,,,,,,,,,,,,,26
ratpack.form,,,3,,,,,,,,,,,,,,,,,,,,,3,
ratpack.func,,,5,,,,,,,,,,,,,,,,,,,,,,5
ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,6,4,
ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,10,10,
ratpack.util,,,5,,,,,,,,,,,,,,,,,,,,,,5
1 package sink source summary sink:bean-validation sink:create-file sink:groovy sink:header-splitting sink:information-leak sink:jexl sink:jndi-injection sink:ldap sink:mvel sink:ognl-injection sink:open-url sink:set-hostname-verifier sink:sql sink:url-open-stream sink:url-redirect sink:xpath sink:xslt sink:xss source:contentprovider source:remote summary:taint summary:value
9 com.esotericsoftware.kryo.io 1 1
10 com.esotericsoftware.kryo5.io 1 1
11 com.fasterxml.jackson.core 1 1
12 com.fasterxml.jackson.databind 5 6 5 6
13 com.google.common.base 85 62 23
14 com.google.common.cache 17 17
15 com.google.common.collect 553 2 551
90 org.xml.sax 1 1
91 org.xmlpull.v1 3 3
92 play.mvc 4 4
93 ratpack.core.form 3 3
94 ratpack.core.handling 6 4 6 4
95 ratpack.core.http 10 10 10 10
96 ratpack.exec 26 26
97 ratpack.form 3 3
98 ratpack.func 5 5
99 ratpack.handling 6 4 6 4
100 ratpack.http 10 10 10 10
101 ratpack.util 5 5

View File

@@ -18,6 +18,6 @@ Java framework & library support
Java Standard Library,``java.*``,3,510,30,13,,,7,,,10
Java extensions,"``javax.*``, ``jakarta.*``",54,552,32,,,4,,1,1,2
`Spring <https://spring.io/>`_,``org.springframework.*``,29,469,91,,,,19,14,,29
Others,"``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.opensymphony.xwork2.ognl``, ``com.unboundid.ldap.sdk``, ``flexjson``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jooq``, ``org.mvel2``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``",7,28,151,,,,14,18,,
Totals,,143,5261,408,13,6,10,107,33,1,66
Others,"``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.opensymphony.xwork2.ognl``, ``com.unboundid.ldap.sdk``, ``flexjson``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jooq``, ``org.mvel2``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``",39,99,151,,,,14,18,,
Totals,,175,5332,408,13,6,10,107,33,1,66

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Added modeling of the `ruamel.yaml` PyPI package, resulting in additional sinks for the _Deserializing untrusted input_ (`py/unsafe-deserialization`) query (since `ruamel.yaml.load` can lead to code execution).

View File

@@ -25,6 +25,7 @@ private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Psycopg2
private import semmle.python.frameworks.PyMySQL
private import semmle.python.frameworks.Rsa
private import semmle.python.frameworks.RuamelYaml
private import semmle.python.frameworks.Simplejson
private import semmle.python.frameworks.SqlAlchemy
private import semmle.python.frameworks.Stdlib

View File

@@ -519,4 +519,32 @@ module Flask {
override DataFlow::Node getValueArg() { none() }
}
/**
* A call to `flask.send_from_directory`.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.send_from_directory
*/
class FlaskSendFromDirectory extends FileSystemAccess::Range, DataFlow::CallCfgNode {
FlaskSendFromDirectory() {
this = API::moduleImport("flask").getMember("send_from_directory").getACall()
}
override DataFlow::Node getAPathArgument() {
result in [this.getArg(_), this.getArgByName(["directory", "filename"])]
}
}
/**
* A call to `flask.send_file`.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.send_file
*/
class FlaskSendFile extends FileSystemAccess::Range, DataFlow::CallCfgNode {
FlaskSendFile() { this = API::moduleImport("flask").getMember("send_file").getACall() }
override DataFlow::Node getAPathArgument() {
result in [this.getArg(0), this.getArgByName("filename_or_fp")]
}
}
}

View File

@@ -0,0 +1,57 @@
/**
* Provides classes modeling security-relevant aspects of the `ruamel.yaml` PyPI package
*
* See
* - https://pypi.org/project/ruamel.yaml/
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides models for the `ruamel.yaml` PyPI package.
*
* See
* - https://pypi.org/project/ruamel.yaml/
*/
private module RuamelYaml {
// Note: `ruamel.yaml` is a fork of the `PyYAML` PyPI package, so that's why the
// interface is so similar.
/**
* A call to any of the loading functions in `yaml` (`load`, `load_all`, `safe_load`, `safe_load_all`)
*
* See https://pyyaml.org/wiki/PyYAMLDocumentation (you will have to scroll down).
*/
private class RuamelYamlLoadCall extends Decoding::Range, DataFlow::CallCfgNode {
string func_name;
RuamelYamlLoadCall() {
func_name in ["load", "load_all", "safe_load", "safe_load_all"] and
this = API::moduleImport("ruamel").getMember("yaml").getMember(func_name).getACall()
}
override predicate mayExecuteInput() {
func_name in ["load", "load_all"] and
// If the `Loader` argument is not set, the default loader will be used, which is
// not safe. The only safe loaders are `SafeLoader` or `BaseLoader` (and their
// variants with C implementation).
not exists(DataFlow::Node loader_arg |
loader_arg in [this.getArg(1), this.getArgByName("Loader")]
|
loader_arg =
API::moduleImport("ruamel")
.getMember("yaml")
.getMember(["SafeLoader", "BaseLoader", "CSafeLoader", "CBaseLoader"])
.getAUse()
)
}
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("stream")] }
override DataFlow::Node getOutput() { result = this }
override string getFormat() { result = "YAML" }
}
}

View File

@@ -9,7 +9,6 @@
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
@@ -41,11 +40,17 @@ private module Yaml {
}
/**
* This function was thought safe from the 5.1 release in 2017, when the default loader was changed to `FullLoader`.
* In 2020 new exploits were found, meaning it's not safe. The Current plan is to change the default to `SafeLoader` in release 6.0
* (as explained in https://github.com/yaml/pyyaml/issues/420#issuecomment-696752389).
* Until 6.0 is released, we will mark `yaml.load` as possibly leading to arbitrary code execution.
* See https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation for more details.
* This function was thought safe from the 5.1 release in 2017, when the default
* loader was changed to `FullLoader` (see
* https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation).
*
* In 2020 new exploits were found, meaning it's not safe. With the 6.0 release (see
* https://github.com/yaml/pyyaml/commit/8cdff2c80573b8be8e8ad28929264a913a63aa33),
* when using `load` and `load_all` you are now required to specify a Loader. But
* from what I (@RasmusWL) can gather, `FullLoader` is not to be considered safe,
* although known exploits have been mitigated (is at least my impression). Also see
* https://github.com/yaml/pyyaml/issues/420#issuecomment-696752389 for more
* details.
*/
override predicate mayExecuteInput() {
func_name in ["full_load", "full_load_all", "unsafe_load", "unsafe_load_all"]
@@ -63,7 +68,7 @@ private module Yaml {
)
}
override DataFlow::Node getAnInput() { result = this.getArg(0) }
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("stream")] }
override DataFlow::Node getOutput() { result = this }

View File

@@ -1,6 +1,17 @@
from flask import Flask, request
from flask import Flask, request, send_from_directory, send_file
app = Flask(__name__)
@app.route("/save-uploaded-file") # $routeSetup="/save-uploaded-file"
def test_taint(): # $requestHandler
request.files['key'].save("path") # $ getAPathArgument="path"
@app.route("/path-injection") # $routeSetup="/path-injection"
def test_path(): # $requestHandler
send_from_directory("filepath","file") # $ getAPathArgument="filepath" getAPathArgument="file"
send_file("file") # $ getAPathArgument="file"
send_from_directory(directory="filepath","file") # $ getAPathArgument="filepath" getAPathArgument="file"
send_from_directory(filename="filepath","file") # $ getAPathArgument="filepath" getAPathArgument="file"
send_file(filename_or_fp="file") # $ getAPathArgument="file"

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,33 @@
import ruamel.yaml
# Unsafe:
ruamel.yaml.load(payload) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
ruamel.yaml.load(stream=payload) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
ruamel.yaml.load(payload, ruamel.yaml.Loader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
# Safe:
ruamel.yaml.load(payload, ruamel.yaml.SafeLoader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML
ruamel.yaml.load(payload, Loader=ruamel.yaml.SafeLoader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML
ruamel.yaml.load(payload, ruamel.yaml.BaseLoader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML
ruamel.yaml.safe_load(payload) # $ decodeInput=payload decodeOutput=ruamel.yaml.safe_load(..) decodeFormat=YAML
################################################################################
# load_all variants
################################################################################
# Unsafe:
ruamel.yaml.load_all(payload) # $ decodeInput=payload decodeOutput=ruamel.yaml.load_all(..) decodeFormat=YAML decodeMayExecuteInput
# Safe:
ruamel.yaml.safe_load_all(payload) # $ decodeInput=payload decodeOutput=ruamel.yaml.safe_load_all(..) decodeFormat=YAML
################################################################################
# C-based loaders with `libyaml`
################################################################################
# Unsafe:
ruamel.yaml.load(payload, ruamel.yaml.CLoader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
# Safe:
ruamel.yaml.load(payload, ruamel.yaml.CSafeLoader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML
ruamel.yaml.load(payload, ruamel.yaml.CBaseLoader) # $ decodeInput=payload decodeOutput=ruamel.yaml.load(..) decodeFormat=YAML

View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# this file doesn't have a .py extension so the extractor doesn't pick it up, so it
# doesn't have to be annotated
# This file is just a Proof of Concept for how code execution can be triggered.
import os
import ruamel.yaml
class Exploit(object):
def __reduce__(self):
return (os.system, ('ls',))
data = Exploit()
serialized_data = ruamel.yaml.dump(data)
# All these will execute `ls`
print("!!! ruamel.yaml.load")
ruamel.yaml.load(serialized_data)
print("!!! ruamel.yaml.load kwarg")
ruamel.yaml.load(stream=serialized_data)
print("!!! ruamel.yaml.load with Loader=ruamel.yaml.Loader")
ruamel.yaml.load(serialized_data, ruamel.yaml.Loader)
print("!!! ruamel.yaml.load with Loader=ruamel.yaml.UnsafeLoader")
ruamel.yaml.load(serialized_data, ruamel.yaml.UnsafeLoader)
print("!!! ruamel.yaml.load with Loader=ruamel.yaml.CLoader")
ruamel.yaml.load(serialized_data, ruamel.yaml.CLoader)
# you need to iterate through the result for it to execute... but it still works
print("!!! ruamel.yaml.load_all")
for _ in ruamel.yaml.load_all(serialized_data):
pass
# check that the safe version is actually safe
print("\n" + "-"*80)
print("safe versions")
print("-" * 80)
print("!!! ruamel.yaml.safe_load")
try:
ruamel.yaml.safe_load(serialized_data)
raise Exception("should not happen")
except ruamel.yaml.constructor.ConstructorError:
pass
print("!!! ruamel.yaml.load with Loader=ruamel.yaml.SafeLoader")
try:
ruamel.yaml.load(serialized_data, ruamel.yaml.SafeLoader)
raise Exception("should not happen")
except ruamel.yaml.constructor.ConstructorError:
pass
print("!!! ruamel.yaml.load with Loader=ruamel.yaml.CSafeLoader")
try:
ruamel.yaml.load(serialized_data, ruamel.yaml.CSafeLoader)
raise Exception("should not happen")
except ruamel.yaml.constructor.ConstructorError:
pass

View File

@@ -2,6 +2,7 @@ import yaml
# Unsafe:
yaml.load(payload) # $decodeInput=payload decodeOutput=yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
yaml.load(stream=payload) # $decodeInput=payload decodeOutput=yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
yaml.load(payload, yaml.Loader) # $decodeInput=payload decodeOutput=yaml.load(..) decodeFormat=YAML decodeMayExecuteInput
yaml.unsafe_load(payload) # $ decodeInput=payload decodeOutput=yaml.unsafe_load(..) decodeFormat=YAML decodeMayExecuteInput
yaml.full_load(payload) # $ decodeInput=payload decodeOutput=yaml.full_load(..) decodeFormat=YAML decodeMayExecuteInput

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
# this file doesn't have a .py extension so the extractor doesn't pick it up, so it
# doesn't have to be annotated
# This file is just a Proof of Concept for how code execution can be triggered.
import os
import yaml
class Exploit(object):
def __reduce__(self):
return (os.system, ('ls',))
data = Exploit()
serialized_data = yaml.dump(data)
# All these will execute `ls`
print("!!! yaml.unsafe_load")
yaml.unsafe_load(serialized_data)
print("!!! yaml.unsafe_load kwarg")
yaml.unsafe_load(stream=serialized_data)
print("!!! yaml.load with Loader=yaml.UnsafeLoader")
yaml.load(serialized_data, yaml.UnsafeLoader)
# you need to iterate through the result for it to execute... but it still works
print("!!! yaml.unsafe_load_all")
for _ in yaml.unsafe_load_all(serialized_data):
pass
# check that the safe version is actually safe
print("\n" + "-"*80)
print("safe versions")
print("-" * 80)
print("!!! yaml.load")
try:
yaml.load(serialized_data)
raise Exception("should not happen")
except yaml.constructor.ConstructorError:
pass
print("!!! yaml.safe_load")
try:
yaml.safe_load(serialized_data)
raise Exception("should not happen")
except yaml.constructor.ConstructorError:
pass
print("!!! yaml.load with Loader=yaml.SafeLoader")
try:
yaml.load(serialized_data, yaml.SafeLoader)
raise Exception("should not happen")
except yaml.constructor.ConstructorError:
pass

BIN
ruby/Cargo.lock generated

Binary file not shown.