VYPR
High severityNVD Advisory· Published Nov 11, 2022· Updated Apr 23, 2025

Arches vulnerable to SQL Injection

CVE-2022-41892

Description

Arches is a web platform for creating, managing, & visualizing geospatial data. Versions prior to 6.1.2, 6.2.1, and 7.1.2 are vulnerable to SQL Injection. With a carefully crafted web request, it's possible to execute certain unwanted sql statements against the database. This issue is fixed in version 7.12, 6.2.1, and 6.1.2. Users are recommended to upgrade as soon as possible. There are no workarounds.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Arches before 6.1.2, 6.2.1, and 7.1.2 contains blind SQL injection flaws in paged_dropdown and get_child_edges, allowing attackers to extract database contents via crafted requests.

Vulnerability

Overview

CVE-2022-41892 is a blind SQL injection vulnerability affecting the Arches web platform for geospatial data management, prior to versions 6.1.2, 6.2.1, and 7.1.2 [1][2]. The root cause is unsafe string interpolation in the construction of SQL queries, particularly in the paged_dropdown endpoint and the get_child_edges function [1][2]. The developer fix replaced direct Python string formatting with parameterized queries using psycopg2's AsIs and %(name)s placeholders, preventing injection [1].

Attack

Vector and Requirements

The vulnerability is exploitable remotely without authentication, as the endpoints are accessible via the web API [2]. An attacker can inject arbitrary SQL by crafting malicious input into parameters such as the query value in paged_dropdown or the relationtypes variable [1][2]. Because these are blind injections, the attacker typically uses time-based payloads (e.g., SLEEP functions) to infer boolean results based on response timing, allowing step-by-step data extraction [2].

Impact

Successful exploitation allows an attacker to execute arbitrary SQL statements against the underlying PostgreSQL database [2]. This can lead to unauthorized reading of sensitive data (including user credentials and protected geospatial records), potential data modification, and possible database compromise, as the database connection runs with the application's privileges [2][3].

Mitigation

Status

The vulnerability was fixed in the stable/7.1.1 branch via commit 7ed53e23a616edf3301d95814d9d64de5e3072a9, and the official fix is included in versions 7.1.2, 6.2.1, and 6.1.2 [1][3]. Users are strongly advised to upgrade immediately; no workarounds exist [3]. The issue was responsibly disclosed by GitHub Security Lab (GHSL-2022-070) in September 2022, and a coordinated fix was published in November 2022 [2].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
archesPyPI
< 6.1.26.1.2
archesPyPI
>= 6.2.0, < 6.2.16.2.1
archesPyPI
>= 7.0.0, < 7.2.07.2.0

Affected products

3
  • archesproject/archesllm-create2 versions
    <7.1.2, <6.2.1, <6.1.2+ 1 more
    • (no CPE)range: <7.1.2, <6.2.1, <6.1.2
    • (no CPE)range: <= 6.1.2
  • ghsa-coords
    Range: < 6.1.2

Patches

1
7ed53e23a616

fixes #9019, #9021, #9026 in stable/7.1.1

https://github.com/archesproject/archesAlexei PetersOct 29, 2022via ghsa
2 files changed · +126 118
  • arches/app/models/concept.py+108 99 modified
    @@ -32,6 +32,8 @@
     from django.utils.translation import ugettext as _
    
     from django.utils.translation import get_language
    
     from django.db import IntegrityError
    
    +from psycopg2.extensions import AsIs
    
    +
    
     import logging
    
     
    
     
    
    @@ -505,13 +507,12 @@ def get_child_edges(
             except:
    
                 return []
    
     
    
    -        languageid = get_language() if languageid is None else languageid
    
    +        # this interpolation is safe because `relationtypes` is hardcoded in all calls, and not accessible via the API
    
             relationtypes = " or ".join(["r.relationtype = '%s'" % (relationtype) for relationtype in relationtypes])
    
    -        depth_limit = "and depth < %s" % depth_limit if depth_limit else ""
    
    -        child_valuetypes = ("','").join(
    
    -            child_valuetypes if child_valuetypes else models.DValueType.objects.filter(category="label").values_list("valuetype", flat=True)
    
    -        )
    
    -        limit_clause = " limit %s offset %s" % (limit, offset) if offset is not None else ""
    
    +        offset_clause = " limit %(limit)s offset %(offset)s" if offset else ""
    
    +        depth_clause = " and depth < %(depth_limit)s" if depth_limit else ""
    
    +
    
    +        cursor = connection.cursor()
    
     
    
             if order_hierarchically:
    
                 sql = """
    
    @@ -525,9 +526,9 @@ def get_child_edges(
                                 WHERE conceptid=r.conceptidto
    
                                 AND valuetype in ('prefLabel')
    
                                 ORDER BY (
    
    -                                CASE WHEN languageid = '{languageid}' THEN 10
    
    -                                WHEN languageid like '{short_languageid}%' THEN 5
    
    -                                WHEN languageid like '{default_languageid}%' THEN 2
    
    +                                CASE WHEN languageid = %(languageid)s THEN 10
    
    +                                WHEN languageid like %(short_languageid)s THEN 5
    
    +                                WHEN languageid like %(default_languageid)s THEN 2
    
                                     ELSE 0
    
                                     END
    
                                 ) desc limit 1
    
    @@ -547,8 +548,8 @@ def get_child_edges(
                                 limit 1
    
                             ) as collector
    
                             FROM relations r
    
    -                        WHERE r.conceptidfrom = '{conceptid}'
    
    -                        and ({relationtypes})
    
    +                        WHERE r.conceptidfrom = %(conceptid)s
    
    +                        and (%(relationtypes)s)
    
                             ORDER BY sortorder, valuesto
    
                         )
    
                         UNION
    
    @@ -559,9 +560,9 @@ def get_child_edges(
                                 WHERE conceptid=r.conceptidto
    
                                 AND valuetype in ('prefLabel')
    
                                 ORDER BY (
    
    -                                CASE WHEN languageid = '{languageid}' THEN 10
    
    -                                WHEN languageid like '{short_languageid}%' THEN 5
    
    -                                WHEN languageid like '{default_languageid}%' THEN 2
    
    +                                CASE WHEN languageid = %(languageid)s THEN 10
    
    +                                WHEN languageid like %(short_languageid)s THEN 5
    
    +                                WHEN languageid like %(default_languageid)s THEN 2
    
                                     ELSE 0
    
                                     END
    
                                 ) desc limit 1
    
    @@ -582,7 +583,7 @@ def get_child_edges(
                             ) as collector
    
                             FROM relations r
    
                             JOIN ordered_relationships b ON(b.conceptidto = r.conceptidfrom)
    
    -                        WHERE ({relationtypes})
    
    +                        WHERE (%(relationtypes)s)
    
                             ORDER BY sortorder, valuesto
    
                         )
    
                     ),
    
    @@ -593,17 +594,17 @@ def get_child_edges(
                             r.collector,
    
                             1 AS depth       ---|NonRecursive Part
    
                             FROM ordered_relationships r
    
    -                        WHERE r.conceptidfrom = '{conceptid}'
    
    -                        and ({relationtypes})
    
    +                        WHERE r.conceptidfrom = %(conceptid)s
    
    +                        and (%(relationtypes)s)
    
                         UNION
    
                             SELECT r.conceptidfrom, r.conceptidto,
    
                             row || '-' || to_char(row_number() OVER (), 'fm000000'),
    
                             r.collector,
    
                             depth+1      ---|RecursivePart
    
                             FROM ordered_relationships r
    
                             JOIN children b ON(b.conceptidto = r.conceptidfrom)
    
    -                        WHERE ({relationtypes})
    
    -                        {depth_limit}
    
    +                        WHERE (%(relationtypes)s)
    
    +                        {depth_clause}
    
                     )
    
     
    
                     {subquery}
    
    @@ -614,70 +615,73 @@ def get_child_edges(
                         FROM (
    
                             SELECT *
    
                             FROM values
    
    -                        WHERE conceptid={recursive_table}.conceptidto
    
    +                        WHERE conceptid=%(recursive_table)s.conceptidto
    
                             AND valuetype in ('prefLabel')
    
                             ORDER BY (
    
    -                            CASE WHEN languageid = '{languageid}' THEN 10
    
    -                            WHEN languageid like '{short_languageid}%' THEN 5
    
    -                            WHEN languageid like '{default_languageid}%' THEN 2
    
    +                            CASE WHEN languageid = %(languageid)s THEN 10
    
    +                            WHEN languageid like %(short_languageid)s THEN 5
    
    +                            WHEN languageid like %(default_languageid)s THEN 2
    
                                 ELSE 0
    
                                 END
    
                             ) desc limit 1
    
                         ) d
    
                     ) as valueto,
    
                     depth, collector, count(*) OVER() AS full_count
    
     
    
    -               FROM {recursive_table} order by row {limit_clause};
    
    -
    
    +               FROM %(recursive_table)s order by row {offset_clause};
    
                 """
    
     
    
    -            subquery = (
    
    -                """, results as (
    
    -                SELECT c.conceptidfrom, c.conceptidto, c.row, c.depth, c.collector
    
    -                FROM children c
    
    -                JOIN values ON(values.conceptid = c.conceptidto)
    
    -                WHERE LOWER(values.value) like '%%%s%%'
    
    -                AND values.valuetype in ('prefLabel')
    
    -                    UNION
    
    -                SELECT c.conceptidfrom, c.conceptidto, c.row, c.depth, c.collector
    
    -                FROM children c
    
    -                JOIN results r on (r.conceptidfrom=c.conceptidto)
    
    -            )"""
    
    -                % query.lower()
    
    -                if query is not None
    
    -                else ""
    
    -            )
    
    +            if query:
    
    +                subquery = """
    
    +                        , results as (
    
    +                            SELECT c.conceptidfrom, c.conceptidto, c.row, c.depth, c.collector
    
    +                            FROM children c
    
    +                            JOIN values ON(values.conceptid = c.conceptidto)
    
    +                            WHERE LOWER(values.value) like %(query)s
    
    +                            AND values.valuetype in ('prefLabel')
    
    +                                UNION
    
    +                            SELECT c.conceptidfrom, c.conceptidto, c.row, c.depth, c.collector
    
    +                            FROM children c
    
    +                            JOIN results r on (r.conceptidfrom=c.conceptidto)
    
    +                        )
    
    +                    """
    
    +            else:
    
    +                subquery = ""
    
    +
    
    +            sql = sql.format(subquery=subquery, offset_clause=offset_clause, depth_clause=depth_clause)
    
     
    
                 recursive_table = "results" if query else "children"
    
    +            languageid = get_language() if languageid is None else languageid
    
     
    
    -            sql = sql.format(
    
    -                conceptid=conceptid,
    
    -                relationtypes=relationtypes,
    
    -                child_valuetypes=child_valuetypes,
    
    -                parent_valuetype=parent_valuetype,
    
    -                depth_limit=depth_limit,
    
    -                limit_clause=limit_clause,
    
    -                subquery=subquery,
    
    -                recursive_table=recursive_table,
    
    -                languageid=languageid,
    
    -                short_languageid=languageid.split("-")[0],
    
    -                default_languageid=settings.LANGUAGE_CODE,
    
    +            cursor.execute(
    
    +                sql,
    
    +                {
    
    +                    "conceptid": conceptid,
    
    +                    "relationtypes": AsIs(relationtypes),
    
    +                    "depth_limit": depth_limit,
    
    +                    "limit": limit,
    
    +                    "offset": offset,
    
    +                    "query": "%" + query.lower() + "%",
    
    +                    "recursive_table": AsIs(recursive_table),
    
    +                    "languageid": languageid,
    
    +                    "short_languageid": languageid.split("-")[0] + "%",
    
    +                    "default_languageid": settings.LANGUAGE_CODE + "%",
    
    +                },
    
                 )
    
    -
    
             else:
    
                 sql = """
    
                     WITH RECURSIVE
    
                         children AS (
    
                             SELECT r.conceptidfrom, r.conceptidto, r.relationtype, 1 AS depth
    
                                 FROM relations r
    
    -                            WHERE r.conceptidfrom = '{conceptid}'
    
    -                            AND ({relationtypes})
    
    +                            WHERE r.conceptidfrom = %(conceptid)s
    
    +                            AND (%(relationtypes)s)
    
                             UNION
    
                                 SELECT r.conceptidfrom, r.conceptidto, r.relationtype, depth+1
    
                                 FROM relations r
    
                                 JOIN children c ON(c.conceptidto = r.conceptidfrom)
    
    -                            WHERE ({relationtypes})
    
    -                            {depth_limit}
    
    +                            WHERE (%(relationtypes)s)
    
    +                            {depth_clause}
    
                         ),
    
                         results AS (
    
                             SELECT
    
    @@ -692,14 +696,15 @@ def get_child_edges(
                                 JOIN children c ON(c.conceptidto = valueto.conceptid)
    
                                 JOIN values valuefrom ON(c.conceptidfrom = valuefrom.conceptid)
    
                                 JOIN d_value_types dtypesfrom ON(dtypesfrom.valuetype = valuefrom.valuetype)
    
    -                        WHERE valueto.valuetype in ('{child_valuetypes}')
    
    -                        AND valuefrom.valuetype in ('{child_valuetypes}')
    
    +                        WHERE valueto.valuetype in (%(child_valuetypes)s)
    
    +                        AND valuefrom.valuetype in (%(child_valuetypes)s)
    
                         )
    
    -                    SELECT distinct {columns}
    
    -                    FROM results {limit_clause}
    
    -
    
    +                    SELECT distinct %(columns)s
    
    +                    FROM results {offset_clause}
    
                 """
    
     
    
    +            sql = sql.format(offset_clause=offset_clause, depth_clause=depth_clause)
    
    +
    
                 if not columns:
    
                     columns = """
    
                         conceptidfrom::text, conceptidto::text,
    
    @@ -710,19 +715,24 @@ def get_child_edges(
                         categoryfrom, categoryto
    
                     """
    
     
    
    -            sql = sql.format(
    
    -                conceptid=conceptid,
    
    -                relationtypes=relationtypes,
    
    -                child_valuetypes=child_valuetypes,
    
    -                columns=columns,
    
    -                depth_limit=depth_limit,
    
    -                limit_clause=limit_clause,
    
    +            cursor.execute(
    
    +                sql,
    
    +                {
    
    +                    "conceptid": conceptid,
    
    +                    "relationtypes": AsIs(relationtypes),
    
    +                    "child_valuetypes": ("','").join(
    
    +                        child_valuetypes
    
    +                        if child_valuetypes
    
    +                        else models.DValueType.objects.filter(category="label").values_list("valuetype", flat=True)
    
    +                    ),
    
    +                    "columns": AsIs(columns),
    
    +                    "depth_limit": depth_limit,
    
    +                    "limit": limit,
    
    +                    "offset": offset,
    
    +                },
    
                 )
    
     
    
    -        cursor = connection.cursor()
    
    -        cursor.execute(sql)
    
    -        rows = cursor.fetchall()
    
    -        return rows
    
    +        return cursor.fetchall()
    
     
    
         def traverse(self, func, direction="down", scope=None, **kwargs):
    
             """
    
    @@ -1176,30 +1186,31 @@ def get_e55_domain(self, conceptid):
     
    
             """
    
             cursor = connection.cursor()
    
    -
    
    -        sql = """
    
    -        WITH RECURSIVE children AS (
    
    -            SELECT d.conceptidfrom, d.conceptidto, c2.value, c2.valueid as valueid, c.value as valueto, c.valueid as valueidto, c.valuetype as vtype, 1 AS depth, array[d.conceptidto] AS conceptpath, array[c.valueid] AS idpath        ---|NonRecursive Part
    
    -                FROM relations d
    
    -                JOIN values c ON(c.conceptid = d.conceptidto)
    
    -                JOIN values c2 ON(c2.conceptid = d.conceptidfrom)
    
    -                WHERE d.conceptidfrom = '{0}'
    
    -                and c2.valuetype = 'prefLabel'
    
    -                and c.valuetype in ('prefLabel', 'sortorder', 'collector')
    
    -                and (d.relationtype = 'member' or d.relationtype = 'hasTopConcept')
    
    -                UNION
    
    -                SELECT d.conceptidfrom, d.conceptidto, v2.value, v2.valueid as valueid, v.value as valueto, v.valueid as valueidto, v.valuetype as vtype, depth+1, (conceptpath || d.conceptidto), (idpath || v.valueid)   ---|RecursivePart
    
    -                FROM relations  d
    
    -                JOIN children b ON(b.conceptidto = d.conceptidfrom)
    
    -                JOIN values v ON(v.conceptid = d.conceptidto)
    
    -                JOIN values v2 ON(v2.conceptid = d.conceptidfrom)
    
    -                WHERE  v2.valuetype = 'prefLabel'
    
    -                and v.valuetype in ('prefLabel','sortorder', 'collector')
    
    -                and (d.relationtype = 'member' or d.relationtype = 'hasTopConcept')
    
    -            ) SELECT conceptidfrom::text, conceptidto::text, value, valueid::text, valueto, valueidto::text, depth, idpath::text, conceptpath::text, vtype FROM children ORDER BY depth, conceptpath;
    
    -        """.format(
    
    -            conceptid
    
    +        cursor.execute(
    
    +            """
    
    +            WITH RECURSIVE children AS (
    
    +                SELECT d.conceptidfrom, d.conceptidto, c2.value, c2.valueid as valueid, c.value as valueto, c.valueid as valueidto, c.valuetype as vtype, 1 AS depth, array[d.conceptidto] AS conceptpath, array[c.valueid] AS idpath        ---|NonRecursive Part
    
    +                    FROM relations d
    
    +                    JOIN values c ON(c.conceptid = d.conceptidto)
    
    +                    JOIN values c2 ON(c2.conceptid = d.conceptidfrom)
    
    +                    WHERE d.conceptidfrom = %s
    
    +                    and c2.valuetype = 'prefLabel'
    
    +                    and c.valuetype in ('prefLabel', 'sortorder', 'collector')
    
    +                    and (d.relationtype = 'member' or d.relationtype = 'hasTopConcept')
    
    +                    UNION
    
    +                    SELECT d.conceptidfrom, d.conceptidto, v2.value, v2.valueid as valueid, v.value as valueto, v.valueid as valueidto, v.valuetype as vtype, depth+1, (conceptpath || d.conceptidto), (idpath || v.valueid)   ---|RecursivePart
    
    +                    FROM relations  d
    
    +                    JOIN children b ON(b.conceptidto = d.conceptidfrom)
    
    +                    JOIN values v ON(v.conceptid = d.conceptidto)
    
    +                    JOIN values v2 ON(v2.conceptid = d.conceptidfrom)
    
    +                    WHERE  v2.valuetype = 'prefLabel'
    
    +                    and v.valuetype in ('prefLabel','sortorder', 'collector')
    
    +                    and (d.relationtype = 'member' or d.relationtype = 'hasTopConcept')
    
    +                ) SELECT conceptidfrom::text, conceptidto::text, value, valueid::text, valueto, valueidto::text, depth, idpath::text, conceptpath::text, vtype FROM children ORDER BY depth, conceptpath;
    
    +            """,
    
    +            [conceptid],
    
             )
    
    +        rows = cursor.fetchall()
    
     
    
             column_names = [
    
                 "conceptidfrom",
    
    @@ -1213,8 +1224,6 @@ def get_e55_domain(self, conceptid):
                 "conceptpath",
    
                 "vtype",
    
             ]
    
    -        cursor.execute(sql)
    
    -        rows = cursor.fetchall()
    
     
    
             class Val(object):
    
                 def __init__(self, conceptid):
    
    
  • arches/app/views/concept.py+18 19 modified
    @@ -380,8 +380,7 @@ def dropdown(request):
     
    
     def paged_dropdown(request):
    
         conceptid = request.GET.get("conceptid")
    
    -    query = request.GET.get("query", None)
    
    -    query = None if query == "" else query
    
    +    query = request.GET.get("query", "")
    
         page = int(request.GET.get("page", 1))
    
         limit = 50
    
         offset = (page - 1) * limit
    
    @@ -405,25 +404,25 @@ def paged_dropdown(request):
                         found = True
    
                         break
    
                 if not found:
    
    -                sql = """
    
    -                    SELECT value, valueid
    
    -                    FROM 
    
    -                    (
    
    -                        SELECT *, CASE WHEN LOWER(languageid) = '{languageid}' THEN 10
    
    -                        WHEN LOWER(languageid) like '{short_languageid}%' THEN 5
    
    -                        ELSE 0
    
    -                        END score
    
    -                        FROM values
    
    -                    ) as vals
    
    -                    WHERE LOWER(value)='{query}' AND score > 0
    
    -                    AND valuetype in ('prefLabel')
    
    -                    ORDER BY score desc limit 1
    
    -                """
    
    -
    
                     languageid = get_language().lower()
    
    -                sql = sql.format(query=query.lower(), languageid=languageid, short_languageid=languageid.split("-")[0])
    
                     cursor = connection.cursor()
    
    -                cursor.execute(sql)
    
    +                cursor.execute(
    
    +                    """
    
    +                        SELECT value, valueid
    
    +                        FROM
    
    +                        (
    
    +                            SELECT *, CASE WHEN LOWER(languageid) = %(languageid)s THEN 10
    
    +                            WHEN LOWER(languageid) like %(short_languageid)s THEN 5
    
    +                            ELSE 0
    
    +                            END score
    
    +                            FROM values
    
    +                        ) as vals
    
    +                        WHERE LOWER(value)=%(query)s AND score > 0
    
    +                        AND valuetype in ('prefLabel')
    
    +                        ORDER BY score desc limit 1
    
    +                    """,
    
    +                    {"languageid": languageid, "short_languageid": languageid.split("-")[0] + "%", "query": query.lower()},
    
    +                )
    
                     rows = cursor.fetchall()
    
     
    
                     if len(rows) == 1:
    
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.