Skip to content

Commit

Permalink
TC-2174 OSV Python ecosystem management
Browse files Browse the repository at this point in the history
Signed-off-by: mrizzi <[email protected]>
  • Loading branch information
mrizzi committed Jan 29, 2025
1 parent b5fca5d commit 7a9990d
Show file tree
Hide file tree
Showing 13 changed files with 697 additions and 0 deletions.
30 changes: 30 additions & 0 deletions etc/test-data/cyclonedx/pypi_aiohttp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"serialNumber": "urn:uuid:a5ddee00-4b86-498c-b7fd-b001b77479d1",
"metadata": {
"timestamp": "2025-01-28T18:14:15.675260000Z",
"component": {
"type": "application",
"name": "1 vulnerability",
"version": "0.1.0"
}
},
"components": [
{
"type": "library",
"name": "aiohttp",
"version": "1.0.5",
"description": "1 vulnerability",
"purl": "pkg:pypi/[email protected]"
},
{
"type": "library",
"name": "opencrx-core-models",
"version": "4.3-alpha-10",
"description": "no vulnerabilities",
"purl": "pkg:maven/org.opencrx/[email protected]"
}
]
}
88 changes: 88 additions & 0 deletions etc/test-data/osv/GHSA-45c4-8wx5-qw6w.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"schema_version": "1.4.0",
"id": "GHSA-45c4-8wx5-qw6w",
"modified": "2024-09-03T21:33:36Z",
"published": "2023-07-20T14:52:00Z",
"aliases": [
"CVE-2023-37276"
],
"summary": "aiohttp.web.Application vulnerable to HTTP request smuggling via llhttp HTTP request parser",
"details": "### Impact\n\naiohttp v3.8.4 and earlier are [bundled with llhttp v6.0.6](https://github.com/aio-libs/aiohttp/blob/v3.8.4/.gitmodules) which is vulnerable to CVE-2023-30589. The vulnerable code is used by aiohttp for its HTTP request parser when available which is the default case when installing from a wheel.\n\nThis vulnerability only affects users of aiohttp as an HTTP server (ie `aiohttp.Application`), you are not affected by this vulnerability if you are using aiohttp as an HTTP client library (ie `aiohttp.ClientSession`).\n\n### Reproducer\n\n```python\nfrom aiohttp import web\n\nasync def example(request: web.Request):\n headers = dict(request.headers)\n body = await request.content.read()\n return web.Response(text=f\"headers: {headers} body: {body}\")\n\napp = web.Application()\napp.add_routes([web.post('/', example)])\nweb.run_app(app)\n```\n\nSending a crafted HTTP request will cause the server to misinterpret one of the HTTP header values leading to HTTP request smuggling.\n\n```console\n$ printf \"POST / HTTP/1.1\\r\\nHost: localhost:8080\\r\\nX-Abc: \\rxTransfer-Encoding: chunked\\r\\n\\r\\n1\\r\\nA\\r\\n0\\r\\n\\r\\n\" \\\n | nc localhost 8080\n\nExpected output:\n headers: {'Host': 'localhost:8080', 'X-Abc': '\\rxTransfer-Encoding: chunked'} body: b''\n\nActual output (note that 'Transfer-Encoding: chunked' is an HTTP header now and body is treated differently)\n headers: {'Host': 'localhost:8080', 'X-Abc': '', 'Transfer-Encoding': 'chunked'} body: b'A'\n```\n\n### Patches\n\nUpgrade to the latest version of aiohttp to resolve this vulnerability. It has been fixed in v3.8.5: [`pip install aiohttp >= 3.8.5`](https://pypi.org/project/aiohttp/3.8.5/)\n\n### Workarounds\n\nIf you aren't able to upgrade you can reinstall aiohttp using `AIOHTTP_NO_EXTENSIONS=1` as an environment variable to disable the llhttp HTTP request parser implementation. The pure Python implementation isn't vulnerable to request smuggling:\n\n```console\n$ python -m pip uninstall --yes aiohttp\n$ AIOHTTP_NO_EXTENSIONS=1 python -m pip install --no-binary=aiohttp --no-cache aiohttp\n```\n\n### References\n\n* https://nvd.nist.gov/vuln/detail/CVE-2023-30589\n* https://hackerone.com/reports/2001873\n",
"severity": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
{
"type": "CVSS_V4",
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"
}
],
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "aiohttp"
},
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0"
},
{
"fixed": "3.8.5"
}
]
}
],
"database_specific": {
"last_known_affected_version_range": "<= 3.8.4"
}
}
],
"references": [
{
"type": "WEB",
"url": "https://github.com/aio-libs/aiohttp/security/advisories/GHSA-45c4-8wx5-qw6w"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-37276"
},
{
"type": "WEB",
"url": "https://github.com/aio-libs/aiohttp/commit/9337fb3f2ab2b5f38d7e98a194bde6f7e3d16c40"
},
{
"type": "WEB",
"url": "https://github.com/aio-libs/aiohttp/commit/9c13a52c21c23dfdb49ed89418d28a5b116d0681"
},
{
"type": "WEB",
"url": "https://hackerone.com/reports/2001873"
},
{
"type": "PACKAGE",
"url": "https://github.com/aio-libs/aiohttp"
},
{
"type": "WEB",
"url": "https://github.com/aio-libs/aiohttp/blob/v3.8.4/.gitmodules"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/aiohttp/PYSEC-2023-120.yaml"
}
],
"database_specific": {
"cwe_ids": [
"CWE-444"
],
"severity": "MODERATE",
"github_reviewed": true,
"github_reviewed_at": "2023-07-20T14:52:00Z",
"nvd_published_at": "2023-07-19T20:15:10Z"
}
}
2 changes: 2 additions & 0 deletions migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ mod m0000810_fix_get_purl;
mod m0000820_create_conversation;
mod m0000830_perf_indexes;
mod m0000840_add_relationship_14_15;
mod m0000850_python_version;

pub struct Migrator;

Expand Down Expand Up @@ -209,6 +210,7 @@ impl MigratorTrait for Migrator {
Box::new(m0000820_create_conversation::Migration),
Box::new(m0000830_perf_indexes::Migration),
Box::new(m0000840_add_relationship_14_15::Migration),
Box::new(m0000850_python_version::Migration),
]
}
}
Expand Down
138 changes: 138 additions & 0 deletions migration/src/m0000850_python_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
#[allow(deprecated)]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();

db.execute_unprepared(
&Query::update()
.table(VersionScheme::Table)
.value(VersionScheme::Id, "python")
.and_where(Expr::col(VersionScheme::Id).eq("pypi"))
.to_owned()
.to_string(PostgresQueryBuilder),
)
.await?;

db.execute_unprepared(include_str!("m0000850_python_version/pythonver_cmp.sql"))
.await
.map(|_| ())?;

db.execute_unprepared(include_str!(
"m0000850_python_version/python_version_matches.sql"
))
.await
.map(|_| ())?;

db.execute_unprepared(include_str!("m0000850_python_version/version_matches.sql"))
.await
.map(|_| ())?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();

db.execute_unprepared(include_str!("m0000670_version_cmp/version_matches.sql"))
.await
.map(|_| ())?;

db.execute_unprepared("drop function python_version_matches")
.await?;

db.execute_unprepared("drop function pythonver_cmp").await?;

insert(
db,
"pypi",
"Python",
Some("https://www.python.org/dev/peps/pep-0440/"),
)
.await?;
update(db, "python", "pypi").await?;
delete(db, "python").await?;

Ok(())
}
}

async fn insert(
db: &SchemaManagerConnection<'_>,
id: &str,
name: &str,
description: Option<&str>,
) -> Result<(), DbErr> {
db.execute(
db.get_database_backend().build(
Query::insert()
.into_table(VersionScheme::Table)
.columns([
VersionScheme::Id,
VersionScheme::Name,
VersionScheme::Description,
])
.values([
SimpleExpr::Value(Value::String(Some(Box::new(id.to_string())))),
SimpleExpr::Value(Value::String(Some(Box::new(name.to_string())))),
SimpleExpr::Value(Value::String(description.map(|e| Box::new(e.to_string())))),
])
.map_err(|e| DbErr::Custom(e.to_string()))?,
),
)
.await?;
Ok(())
}

async fn update(
db: &SchemaManagerConnection<'_>,
current_version_scheme: &str,
update_to_version_scheme: &str,
) -> Result<(), DbErr> {
db.execute(
db.get_database_backend().build(
Query::update()
.table(VersionRange::Table)
.value(
VersionRange::VersionSchemeId,
update_to_version_scheme.to_string(),
)
.and_where(
Expr::col(VersionRange::VersionSchemeId).eq(current_version_scheme.to_string()),
),
),
)
.await?;
Ok(())
}

async fn delete(db: &SchemaManagerConnection<'_>, version_scheme: &str) -> Result<(), DbErr> {
db.execute(
db.get_database_backend().build(
Query::delete()
.from_table(VersionScheme::Table)
.and_where(Expr::col(VersionScheme::Id).eq(version_scheme.to_string())),
),
)
.await?;
Ok(())
}

#[derive(DeriveIden)]
pub enum VersionScheme {
Table,
Id,
Name,
Description,
}

#[derive(DeriveIden)]
enum VersionRange {
Table,
VersionSchemeId,
}
52 changes: 52 additions & 0 deletions migration/src/m0000850_python_version/python_version_matches.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
create or replace function python_version_matches(version_p text, range_p version_range)
returns bool
as
$$
declare
low_end integer;
high_end integer;
begin
if range_p.low_version is not null then
low_end := pythonver_cmp(version_p, range_p.low_version);
end if;

if low_end is not null then
if range_p.low_inclusive then
if low_end < 0 then
return false;
end if;
else
if low_end <= 0 then
return false;
end if;
end if;

end if;


if range_p.high_version is not null then
high_end := pythonver_cmp(version_p, range_p.high_version);
end if;

if high_end is not null then
if range_p.high_inclusive then
if high_end > 0 then
return false;
end if;
else
if high_end >= 0 then
return false;
end if;
end if;
end if;

if low_end is null and high_end is null then
return false;
end if;

return true;

end
$$
language plpgsql immutable;

Loading

0 comments on commit 7a9990d

Please sign in to comment.