Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TC-2174 OSV Python ecosystem management #1212

Merged
merged 2 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
db.get_database_backend().build(
Query::update()
.table(VersionScheme::Table)
.value(VersionScheme::Id, "python")
.and_where(Expr::col(VersionScheme::Id).eq("pypi")),
),
)
.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