Coordinated Disclosure Timeline

Summary

Streamlit-geospatial project contains several remote code execution and blind server-side request forgery vulnerabilities.

Project

streamlit-geospatial

Tested Version

latest

Details

Issue 1: Remote code execution in pages/1_πŸ“·_Timelapse.py Any Earth Engine ImageCollection option palette (GHSL-2024-100)

The palette variable in pages/1_πŸ“·_Timelapse.py takes user input, which is later used in the eval() function on line 380, leading to remote code execution.

  palette = st.text_area(
      "Enter a custom palette:",
      palette_values,
  )
  st.write(
      cm.plot_colormap(cmap=palette_options, return_fig=True)
  )
  st.session_state["palette"] = eval(palette)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to πŸ“· Timelapse tab and:
    • in Select a satellite image collection: choose Any Earth Engine Image Collection
    • in Enter an ee.ImageCollection asset ID: type ECMWF/CAMS/NRT
  3. Open Customize band combination and color palette and paste into Enter a custom palette:
    __import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 2: Remote code execution in pages/1_πŸ“·_Timelapse.py Any Earth Engine ImageCollection option vis_params (GHSL-2024-101)

The vis_params variable on line 383 or line 390 in pages/1_πŸ“·_Timelapse.py takes user input, which is later used in the eval() function on line 395, leading to remote code execution.

 if bands:
    vis_params = st.text_area(
        "Enter visualization parameters",
        "{'bands': ["
        + ", ".join([f"'{band}'" for band in bands])
        + "]}",
    )
else:
    vis_params = st.text_area(
        "Enter visualization parameters",
        "{}",
    )
try:
    st.session_state["vis_params"] = eval(vis_params)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to πŸ“· Timelapse tab and:
    • in Select a satellite image collection: choose Any Earth Engine Image Collection
    • in Enter an ee.ImageCollection asset ID: type ECMWF/CAMS/NRT
  3. Open Customize band combination and color palette and paste into Enter visualization parameters:
    __import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 3: Remote code execution in pages/1_πŸ“·_Timelapse.py MODIS Gap filled Land Surface Temperature Daily option (GHSL-2024-102)

The palette variable on line 430 in pages/1_πŸ“·_Timelapse.py takes user input, which is later used in the eval() function on line 435, leading to remote code execution.

palette = st.text_area(
    "Enter a custom palette:",
    palette_values,
)
st.write(cm.plot_colormap(cmap=palette_options, return_fig=True))
st.session_state["palette"] = eval(palette)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to πŸ“· Timelapse tab and in Select a satellite image collection: choose MODIS Gap filled Land Surface Temperature Daily
  3. Paste into Enter a custom palette:
    __import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 4: Remote code execution in pages/1_πŸ“·_Timelapse.py MODIS Ocean Color SMI option palette (GHSL-2024-103)

The palette variable on line 488 in pages/1_πŸ“·_Timelapse.py takes user input, which is later used in the eval() function on line 493, leading to remote code execution.

palette = st.text_area(
    "Enter a custom palette:",
    palette_values,
)
st.write(cm.plot_colormap(cmap=palette_options, return_fig=True))
st.session_state["palette"] = eval(palette)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to πŸ“· Timelapse tab and in Select a satellite image collection: choose MODIS Ocean Color SMI
  3. Paste into Enter a custom palette:
    __import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 5: Remote code execution in pages/1_πŸ“·_Timelapse.py MODIS Ocean Color SMI option vis_params (GHSL-2024-104)

The vis_params variable on line 1254 in pages/1_πŸ“·_Timelapse.py takes user input, which is later used in the eval() function on line 1345, leading to remote code execution.

vis_params = st.text_area(
    "Enter visualization parameters",
    "",
    help="Enter a string in the format of a dictionary, such as '{'min': 23, 'max': 32}'",
)

--- snip

        elif collection == "MODIS Ocean Color SMI":
            if vis_params.startswith("{") and vis_params.endswith(
                "}"
            ):
                vis_params = eval(vis_params)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to πŸ“· Timelapse tab and:
    • in Select a satellite image collection: choose MODIS Ocean Color SMI
    • in Select a sample ROI or upload a GeoJSON file choose World
  3. Open Customize timelapse and paste into Enter visualization parameters:
    {__import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')}
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 6: Remote code execution in pages/10_🌍_Earth_Engine_Datasets.py (GHSL-2024-105)

The vis_params variable on line 115 in pages/10_🌍_Earth_Engine_Datasets.py takes user input, which is later used in the eval() function on line 126, leading to remote code execution.

vis_params = st.text_input(
    "Enter visualization parameters as a dictionary", {}
)
layer_name = st.text_input("Enter a layer name", uid)
button = st.button("Add dataset to map")
if button:
    vis = {}
    try:
        if vis_params.strip() == "":
            # st.error("Please enter visualization parameters")
            vis_params = "{}"
        vis = eval(vis_params)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to 🌍 Earth Engine Datasets tab and in Enter a keyword to search (e.g., elevation) type elevation
  3. Paste into Enter visualization parameters as a dictionary:
    __import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 7: Remote code execution in pages/8_🏜️_Raster_Data_Visualization.py (GHSL-2024-107)

The vis_params variable on line 80 in 8_🏜️_Raster_Data_Visualization.py takes user input, which is later used in the eval() function on line 86, leading to remote code execution.

add_params = st.checkbox("Add visualization parameters")
if add_params:
    vis_params = st.text_area("Enter visualization parameters", "{}")
else:
    vis_params = {}

if len(vis_params) > 0:
    try:
        vis_params = eval(vis_params)

Impact

This issue may lead to remote code execution.

PoC

  1. Start streamlit-geospatial
  2. Go to 🏜️ Raster Data Visualization tab and select Add visualization parameters.
  3. Paste into Enter visualization parameters:
    __import__('code').InteractiveInterpreter().runsource('import os;os.system(\"echo $(uname -a) > foobar.txt\")')
    
  4. Observe in the server, that a file foobar.txt has been created, and contains the output of the command uname -a.

Issue 8: Blind SSRF in pages/7_πŸ“¦_Web_Map_Service.py (GHSL-2024-106)

The url variable on line 47 takes user Β input, which is passed to get_layers function, in which url is used with get_wms_layer method. get_wms_layer method creates a request to arbitrary destinations, leading to blind server-side request forgery.

@st.cache_data
def get_layers(url):
    options = leafmap.get_wms_layers(url)
    return options

--- snip

        url = st.text_input(
            "Enter a WMS URL:", value="https://services.terrascope.be/wms/v2"
        )
        empty = st.empty()

        if url:
            options = get_layers(url)

Impact

This issue allows for sending requests on behalf of the streamlit-geospatial server and can be leveraged to probe for other vulnerabilities on the server itself or on other back-end systems on the internal network, that the streamlit-geospatial server can reach.

See also: https://portswigger.net/web-security/ssrf/blind

PoC

  1. Start streamlit-geospatial.
  2. Create a new folder with a file file.txt. Start a simple HTTP server in the folder by executing python -m http.server. It will start a server on 127.0.0.1:8000.
  3. Go to πŸ“¦ Web Map Service tab and in Enter a WMS URL: type http://127.0.0.1:8000/file.txt
  4. Observe that a request to the file was logged in the python server.

Issue 9: Blind SSRF in pages/9_πŸ”²_Vector_Data_Visualization.py (GHSL-2024-108)

The url variable on line 63 takes user input, which is later passed to the gpd.read_file method. gpd.read_file method creates a request to arbitrary destinations, leading to blind server-side request forgery.

    url = empty.text_input(
        "Enter a HTTP URL to a Cloud Optimized GeoTIFF (COG)",
        cog,
    )

    if url:
        try:
            options = leafmap.cog_bands(url)

Impact

This issue allows for sending requests on behalf of the streamlit-geospatial server and can be leveraged to probe for other vulnerabilities on the server itself or on other back-end systems on the internal network, that the streamlit-geospatial server can reach.

See also: https://portswigger.net/web-security/ssrf/blind

PoC

  1. Start streamlit-geospatial.
  2. Create a new folder with a file file.txt. Start a simple HTTP server in the folder by executing python -m http.server. It will start a server on 127.0.0.1:8000.
  3. Go to πŸ”² Vector Data Visualization tab and in Enter a URL to a vector dataset type http://127.0.0.1:8000/file.txt
  4. Observe that a request to the file was logged in the python server.

CVE

Credit

These issues were discovered and reported by GHSL team member @sylwia-budzynska (Sylwia Budzynska).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2024-100, GHSL-2024-101, GHSL-2024-102, GHSL-2024-103, GHSL-2024-104, GHSL-2024-105, GHSL-2024-106, GHSL-2024-107, or GHSL-2024-108 in any communication regarding these issues.