mirror of
https://github.com/github/codeql.git
synced 2026-04-22 23:35:14 +02:00
fix a commit mistake
This commit is contained in:
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* @name User-controlled file decompression
|
||||
* @description User-controlled data that flows into decompression library APIs without checking the compression rate is dangerous
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.8
|
||||
* @precision medium
|
||||
* @id cs/user-controlled-file-decompression
|
||||
* @tags security
|
||||
* experimental
|
||||
* external/cwe/cwe-409
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.security.dataflow.flowsources.Remote
|
||||
|
||||
/**
|
||||
* A data flow source for unsafe Decompression extraction.
|
||||
*/
|
||||
abstract class DecompressionSource extends DataFlow::Node { }
|
||||
|
||||
class ZipOpenReadSource extends DecompressionSource {
|
||||
ZipOpenReadSource() {
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget().hasQualifiedName("System.IO.Compression", "ZipFile", ["OpenRead", "Open"]) and
|
||||
this.asExpr() = mc.getArgument(0) and
|
||||
not mc.getArgument(0).getType().isConst()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A path argument to a call to the `ZipArchive` constructor call. */
|
||||
class ZipArchiveArgSource extends DecompressionSource {
|
||||
ZipArchiveArgSource() {
|
||||
exists(ObjectCreation oc |
|
||||
oc.getTarget().getDeclaringType().hasQualifiedName("System.IO.Compression", "ZipArchive")
|
||||
|
|
||||
this.asExpr() = oc.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for unsafe zip extraction.
|
||||
*/
|
||||
abstract class DecompressionSink extends DataFlow::Node { }
|
||||
|
||||
/** A Caller of the `ExtractToFile` method. */
|
||||
class ExtractToFileCallSink extends DecompressionSink {
|
||||
ExtractToFileCallSink() {
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget().hasQualifiedName("System.IO.Compression", "ZipFileExtensions", "ExtractToFile") and
|
||||
this.asExpr() = mc.getArgumentForName("source")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A Qualifier of the `Open()` method. */
|
||||
class OpenCallSink extends DecompressionSink {
|
||||
OpenCallSink() {
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget().hasQualifiedName("System.IO.Compression", "ZipArchiveEntry", "Open") and
|
||||
this.asExpr() = mc.getQualifier()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A Call to the `GZipStreamSink` first arugument of Constructor Call . */
|
||||
class GZipStreamSink extends DecompressionSink, DecompressionSource {
|
||||
GZipStreamSink() {
|
||||
exists(Constructor mc |
|
||||
mc.getDeclaringType().hasQualifiedName("System.IO.Compression", "GZipStream") and
|
||||
this.asExpr() = mc.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for Decompression Bomb.
|
||||
*/
|
||||
private module DecompressionBombConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source instanceof DecompressionSource
|
||||
or
|
||||
source instanceof RemoteFlowSource
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionSink }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
// var node2 = new ZipArchive(node1, ZipArchiveMode.Read);
|
||||
exists(ObjectCreation oc |
|
||||
oc.getTarget().getDeclaringType().hasQualifiedName("System.IO.Compression", "ZipArchive") and
|
||||
node2.asExpr() = oc and
|
||||
node1.asExpr() = oc.getArgumentForName("stream")
|
||||
)
|
||||
or
|
||||
// var node2 = node1.ExtractToFile("./output.txt", true)
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget().hasQualifiedName("System.IO.Compression", "ZipFileExtensions", "ExtractToFile") and
|
||||
node2.asExpr() = mc and
|
||||
node1.asExpr() = mc.getArgumentForName("source")
|
||||
)
|
||||
or
|
||||
// var node2 = node1.OpenReadStream()
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget().hasQualifiedName("Microsoft.AspNetCore.Http", "IFormFile", "OpenReadStream") and
|
||||
node2.asExpr() = mc and
|
||||
node1.asExpr() = mc.getQualifier()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module DecompressionBomb = TaintTracking::Global<DecompressionBombConfig>;
|
||||
|
||||
import DecompressionBomb::PathGraph
|
||||
|
||||
from DecompressionBomb::PathNode source, DecompressionBomb::PathNode sink
|
||||
where DecompressionBomb::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
|
||||
"potentially untrusted source"
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Extracting Compressed files with any compression algorithm like gzip can cause to denial of service attacks.</p>
|
||||
<p>Attackers can compress a huge file which created by repeated similiar byte and convert it to a small compressed file.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>When you want to decompress a user-provided compressed file you must be careful about the decompression ratio or read these files within a loop byte by byte to be able to manage the decompressed size in each cycle of the loop.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>A good Blog Post about decompression bombs and recommended method is already written by Gérald Barré in <a href="https://www.meziantou.net/prevent-zip-bombs-in-dotnet.htm">this</a> blog post</p>
|
||||
|
||||
<references>
|
||||
|
||||
<li>
|
||||
<a href="https://www.bamsoftware.com/hacks/zipbomb/">A great research to gain more impact by this kind of attack</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,62 +0,0 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.security.dataflow.flowsources.Remote
|
||||
|
||||
/** A data flow source of remote user input by Form File (ASP.NET unvalidated request data). */
|
||||
class FormFile extends AspNetRemoteFlowSource {
|
||||
FormFile() {
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget()
|
||||
.hasQualifiedName(["Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Http.Features"],
|
||||
"IFormFile", ["OpenReadStream", "ContentType", "ContentDisposition", "Name", "FileName"]) and
|
||||
this.asExpr() = mc
|
||||
)
|
||||
or
|
||||
exists(MethodCall mc |
|
||||
mc.getTarget()
|
||||
.hasQualifiedName(["Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Http.Features"],
|
||||
"IFormFile", "CopyTo") and
|
||||
this.asParameter() = mc.getTarget().getParameter(0)
|
||||
)
|
||||
or
|
||||
exists(Property fa |
|
||||
fa.getDeclaringType()
|
||||
.hasQualifiedName(["Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Http.Features"],
|
||||
"IFormFile") and
|
||||
fa.hasName(["ContentType", "ContentDisposition", "Name", "FileName"]) and
|
||||
this.asExpr() = fa.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() {
|
||||
result = "ASP.NET unvalidated request data from multipart request"
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow source of remote user input by Form (ASP.NET unvalidated request data). */
|
||||
class FormCollection extends AspNetRemoteFlowSource {
|
||||
FormCollection() {
|
||||
exists(Property fa |
|
||||
fa.getDeclaringType().hasQualifiedName("Microsoft.AspNetCore.Http", "IFormCollection") and
|
||||
fa.hasName("Keys") and
|
||||
this.asExpr() = fa.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() {
|
||||
result = "ASP.NET unvalidated request data from multipart request Form Keys"
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow source of remote user input by Headers (ASP.NET unvalidated request data). */
|
||||
class HeaderDictionary extends AspNetRemoteFlowSource {
|
||||
HeaderDictionary() {
|
||||
exists(Property fa |
|
||||
fa.getDeclaringType().hasQualifiedName("Microsoft.AspNetCore.Http", "IHeaderDictionary") and
|
||||
this.asExpr() = fa.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() {
|
||||
result = "ASP.NET unvalidated request data from Headers of request"
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using System.IO.Compression;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
namespace MultipartFormWebAPITest.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ZipFile1Controller : ControllerBase
|
||||
{
|
||||
// POST api/<ImagesController>
|
||||
[HttpPost]
|
||||
public string Post(List<IFormFile> files)
|
||||
{
|
||||
if (!Request.ContentType!.StartsWith("multipart/form-data"))
|
||||
return "400";
|
||||
if (files.Count == 0)
|
||||
return "400";
|
||||
foreach (var formFile in files)
|
||||
{
|
||||
using var readStream = formFile.OpenReadStream();
|
||||
if (readStream.Length == 0) return "400";
|
||||
ZipHelpers.Bomb3(readStream);
|
||||
ZipHelpers.Bomb2(formFile.FileName);
|
||||
ZipHelpers.Bomb1(formFile.FileName);
|
||||
}
|
||||
var tmp = Request.Form["aa"];
|
||||
var tmp2 = Request.Form.Keys;
|
||||
// when we don't have only one file as body
|
||||
ZipHelpers.Bomb3(Request.Body);
|
||||
ZipHelpers.Bomb2(Request.Query["param1"].ToString());
|
||||
var headers = Request.Headers;
|
||||
ZipHelpers.Bomb1(headers.ETag);
|
||||
return "200";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ZipHelpers
|
||||
{
|
||||
public static void Bomb3(Stream compressedFileStream)
|
||||
{
|
||||
// using FileStream compressedFileStream = File.Open(CompressedFileName, FileMode.Open);
|
||||
// using FileStream outputFileStream = File.Create(DecompressedFileName);
|
||||
using var decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress);
|
||||
using var ms = new MemoryStream();
|
||||
decompressor.CopyTo(ms);
|
||||
}
|
||||
|
||||
public static void Bomb2(string filename)
|
||||
{
|
||||
using var zipToOpen = new FileStream(filename, FileMode.Open);
|
||||
using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read);
|
||||
foreach (var entry in archive.Entries) entry.ExtractToFile("./output.txt", true); // Sensitive
|
||||
}
|
||||
|
||||
public static void Bomb1(string filename)
|
||||
{
|
||||
const long maxLength = 10 * 1024 * 1024; // 10MB
|
||||
// var filename = "/home/am/0_WorkDir/Payloads/Bombs/bombs-bones-codes-BH-2016/archives/evil-headers/10GB.zip";
|
||||
using var zipFile = ZipFile.OpenRead(filename);
|
||||
// Quickly check the value from the zip header
|
||||
var declaredSize = zipFile.Entries.Sum(entry => entry.Length);
|
||||
if (declaredSize > maxLength)
|
||||
throw new Exception("Archive is too big");
|
||||
foreach (var entry in zipFile.Entries)
|
||||
{
|
||||
using var entryStream = entry.Open();
|
||||
// Use MaxLengthStream to ensure we don't read more than the declared length
|
||||
using var maxLengthStream = new MaxLengthStream(entryStream, entry.Length);
|
||||
// Be sure to use the maxLengthSteam variable to read the content of the entry, not entryStream
|
||||
using var ms = new MemoryStream();
|
||||
maxLengthStream.CopyTo(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MaxLengthStream : Stream
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private long _length;
|
||||
|
||||
public MaxLengthStream(Stream stream, long maxLength)
|
||||
{
|
||||
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
MaxLength = maxLength;
|
||||
}
|
||||
|
||||
private long MaxLength { get; }
|
||||
|
||||
public override bool CanRead => _stream.CanRead;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => _stream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var result = _stream.Read(buffer, offset, count);
|
||||
_length += result;
|
||||
if (_length > MaxLength)
|
||||
throw new Exception("Stream is larger than the maximum allowed size");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO ReadAsync
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_stream.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
experimental/CWE-502-DecompressionBombs/DecompressionBomb.ql
|
||||
Reference in New Issue
Block a user