CVE-2024-21642 (Python, published 2024-01-05T22:15Z)
Description: value: D-Tale is a visualizer for Pandas data structures. Users hosting versions D-Tale prior to 3.9.0 publicly can be vulnerable to server-side request forgery (SSRF), allowing attackers to access files on the server. Users should upgrade to version 3.9.0, where the `Load From the Web` input is turned off by default. The only workaround for versions earlier than 3.9.0 is to only host D-Tale to trusted users.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Repository: https://github.com/man-group/dtale
Files changed:
.circleci/config.yml
README.md
docs/CONFIGURATION.md
dtale/__init__.py
dtale/app.py
dtale/config.py
dtale/datasets.py
dtale/global_state.py
dtale/templates/dtale/base.html
dtale/views.py
frontend/static/__tests__/dtale/upload/Upload.test.support.tsx
frontend/static/__tests__/dtale/upload/upload-url-test.tsx
frontend/static/__tests__/reducers/dtale-test.tsx
frontend/static/__tests__/test-utils.tsx
frontend/static/popups/create/LabeledInput.tsx
frontend/static/popups/filter/FilterPopup.tsx
frontend/static/popups/upload/Upload.tsx
frontend/static/redux/reducers/app/settings.ts
frontend/static/redux/selectors.ts
frontend/static/redux/state/AppState.ts
requirements-test.txt
requirements.txt
tests/dtale/config/dtale.ini
tests/dtale/config/dtale_missing_props.ini
tests/dtale/config/test_config.py
tests/dtale/test_upload.py
tests/dtale/test_views.py
Commit message: Updated web data uploads to be turned off by default


Vulnerable code:
    try:
        from pandas._testing import makeTimeDataFrame

        return makeTimeDataFrame(), None
    except ImportError:
        from pandas.util.testing import makeTimeDataFrame

        return makeTimeDataFrame(), None
  public async setupWrapper(): Promise<Element> {
      const { container } = render(<Upload />);
  inputOptions?: Partial<React.HTMLAttributes<HTMLInputElement>>;
  '- add "enable_custom_filters = False" to the [app] section of your dtale.ini config file',
      <div className="form-group row">
        <label className="col-md-3 col-form-label text-right">{t('Data Type')}</label>
        <div className="col-md-8 p-0">
          <ButtonToggle
            options={Object.values(DataType).map((value) => ({ value, label: value.toUpperCase() }))}
            defaultValue={dataType}
            update={setDataType}
          />
      </div>
      <LabeledInput label="URL" value={url} setter={setUrl} />
      <LabeledInput
        label={
          <React.Fragment>
            {t('Proxy')}
            <small className="pl-3">{t('(Optional)')}</small>
          </React.Fragment>
        }
        value={proxy}
        setter={setProxy}
      />
pytest-server-fixtures
scikit-learn; python_version > '3.7'
scipy; python_version >= '3.9'

Fixed code:
            pip install pillow
            if [ "${CIRCLE_JOB}" == "build_2_7" ]; then
              pip install backports.functools-lru-cache==1.6.6
            fi
**Starting with version 3.8.1 web uploads will be turned off by default.
Web uploads are vulnerable to blind server side request forgery, please only use in trusted environments.**

**You can turn this feature on by doing one of the following:**
 - **add `enable_web_uploads=True` to your `dtale.show` call**
 - **add `enable_web_uploads = False` to the [app] section of your dtale.ini config file ([more info](https://github.com/man-group/dtale/blob/master/docs/CONFIGURATION.md))**
 - **run this code before calling dtale.show:**
```python
import dtale.global_state as global_state
global_state.set_app_settings(dict(enable_web_uploads=True))
```


enable_web_uploads = False
    ENABLE_WEB_UPLOADS = False
    :param enable_web_uploads: If true, this will enable users to upload files using URLs from the UI
    :type enable_web_uploads: bool, optional
            enable_web_uploads=final_options.get("enable_web_uploads"),
    enable_web_uploads = get_config_val(
        config,
        curr_app_settings,
        "enable_web_uploads",
        section="app",
        getter="getboolean",
    )
            enable_web_uploads=enable_web_uploads,
        enable_web_uploads=None,
import numpy as np
import string
from datetime import datetime
    def series_data():
        if hasattr(np.random, "default_rng"):
            return np.random.default_rng(2).standard_normal(30)
        return np.random.randn(30)

    cols = string.ascii_uppercase[:4]
    data = {
        c: pd.Series(
            series_data(),
            index=pd.DatetimeIndex(
                pd.date_range(datetime(2000, 1, 1), periods=30, freq="B")
            ),
        )
        for c in cols
    }
    return pd.DataFrame(data), None
    "enable_web_uploads": False,
    if settings.get("enable_web_uploads") is not None:
        instance_updates["enable_web_uploads"] = settings.get("enable_web_uploads")
        if instance_updates["enable_web_uploads"]:
            logger.warning(
                (
                    "Turning on Web uploads. Web uploads are vulnerable to blind server side request forgery, please "
                    "only use in trusted environments."
                )
            )
        <input type="hidden" id="enable_web_uploads" value="{{enable_web_uploads}}" />
        * enable_web_uploads - if True, allow users to upload files using URLs from the UI
    enable_web_uploads=None,
            enable_web_uploads=enable_web_uploads,
                enable_web_uploads=enable_web_uploads,
        if enable_web_uploads is not None:
            base_settings["enable_web_uploads"] = enable_web_uploads
        if global_state.load_flag(data_id, "enable_web_uploads", False):
            logger.warning(
                (
                    "Web uploads enabled. Web uploads are vulnerable to blind server side request forgery, please "
                    "only use in trusted environments."
                )
            )
    enable_web_uploads = global_state.load_flag(data_id, "enable_web_uploads", False)
        enable_web_uploads=enable_web_uploads,
    if not global_state.get_app_settings().get("enable_web_uploads", False):
        return jsonify(
            dict(
                success=False,
                error=(
                    "Web uploads not enabled! Web uploads are vulnerable to blind server side request forgery, please "
                    "only use in trusted environments."
                ),
            )
        )

import { Provider } from 'react-redux';
import { Store } from 'redux';
import { buildInnerHTML } from '../../test-utils';
  public store: Store;
    this.store = reduxUtils.createDtaleStore();
   * @param overrides redux overrides
  public async setupWrapper(overrides?: Record<string, string>): Promise<Element> {
    this.store = reduxUtils.createDtaleStore();
    buildInnerHTML({ enableWebUploads: 'True', ...overrides }, this.store);
      const { container } = render(
        <Provider store={this.store}>
          <Upload />
        </Provider>,
        {
          container: document.getElementById('content') ?? undefined,
        },
      );
import { screen } from '@testing-library/react';

import { DISABLED_URL_UPLOADS_MSG } from '../../../popups/upload/Upload';

import * as TestSupport from './Upload.test.support';

describe('Upload', () => {
  const { close, location, open, opener } = window;
  const spies = new TestSupport.Spies();

  beforeEach(async () => {
    delete (window as any).location;
    delete (window as any).close;
    delete (window as any).open;
    delete window.opener;
    (window as any).location = {
      reload: jest.fn(),
      pathname: '/dtale/column/1',
      href: '',
      assign: jest.fn(),
    };
    window.close = jest.fn();
    window.open = jest.fn();
    window.opener = { location: { assign: jest.fn(), pathname: '/dtale/column/1' } };
    spies.setupMockImplementations();
    await spies.setupWrapper({ enableWebUploads: 'False' });
  });

  afterEach(() => spies.afterEach());

  afterAll(() => {
    spies.afterAll();
    window.location = location;
    window.close = close;
    window.open = open;
    window.opener = opener;
  });

  const upload = (): HTMLElement => screen.getByTestId('upload');

  it('renders successfully', async () => {
    expect(upload()).toBeDefined();
  });

  it('DataViewer: disabled web uploads', async () => {
    expect(upload().getElementsByClassName('form-group')[0].textContent).toBe(DISABLED_URL_UPLOADS_MSG);
  });
});
      enableWebUploads: false,
    buildHidden('enable_web_uploads', props.enableWebUploads ?? HIDE_SHUTDOWN),
  inputOptions?: Partial<React.AllHTMLAttributes<HTMLInputElement>>;
  '- add "enable_custom_filters = True" to the [app] section of your dtale.ini config file',
import { useSelector } from 'react-redux';
import { selectEnableWebUploads } from '../../redux/selectors';
export const DISABLED_URL_UPLOADS_MSG = [
  'Web uploads are currently disabled.  This feature is only for trusted environments, in order to unlock this ',
  'feature you must do one of the following:',
].join('');

  const enableWebUploads = useSelector(selectEnableWebUploads);
      {!enableWebUploads && (
        <div className="form-group row">
          <div className="col-md-2 p-0" />
          <div className="col-md-8 col-form-label text-left">
            <label>{DISABLED_URL_UPLOADS_MSG}</label>
            <ul>
              <li>
                {`add `}
                <code>enable_web_uploads=True</code>
                {` to your `}
                <code className="font-weight-bold">dtale.show</code>
                {` call`}
              </li>
              <li>
                {`run this code before calling `}
                <code className="font-weight-bold">dtale.show</code>
                <pre>
                  {`import dtale.global_state as global_state\n`}
                  {`global_state.set_app_settings(dict(enable_web_uploads=True))`}
                </pre>
              </li>
              <li>
                {`add `}
                <code>enable_web_uploads = True</code>
                {` to the [app] section of your dtale.ini config file`}
              </li>
            </ul>
          </div>
          <div className="col-md-2 p-0" />
      )}
      {enableWebUploads && (
        <>
          <div className="form-group row">
            <label className="col-md-3 col-form-label text-right">{t('Data Type')}</label>
            <div className="col-md-8 p-0">
              <ButtonToggle
                options={Object.values(DataType).map((value) => ({ value, label: value.toUpperCase() }))}
                defaultValue={dataType}
                update={setDataType}
              />
            </div>
          </div>
          <LabeledInput label="URL" value={url} setter={setUrl} />
          <LabeledInput
            label={
              <React.Fragment>
                {t('Proxy')}
                <small className="pl-3">{t('(Optional)')}</small>
              </React.Fragment>
            }
            value={proxy}
            setter={setProxy}
          />
        </>
      )}
export const enableWebUploads = (state = false, action: AppActionTypes): boolean => {
  switch (action.type) {
    case ActionType.INIT_PARAMS:
      return toBool(getHiddenValue('enable_web_uploads'));
    case ActionType.LOAD_PREVIEW:
      return false;
    default:
      return state;
  }
};

export const selectEnableWebUploads = (state: AppState): boolean => state.enableWebUploads;
  enableWebUploads: boolean;
pytest-server-fixtures<=1.7.0; python_version == '2.7'
pytest-server-fixtures; python_version >= '3.6'
scikit-learn<=1.3.2; python_version == '3.8'
scikit-learn; python_version > '3.8'
scipy!=1.12.0rc1; python_version >= '3.9'
enable_web_uploads = False
enable_web_uploads = False
        "enable_web_uploads": True,
        assert settings["enable_web_uploads"]
        assert not settings["enable_web_uploads"]
        "enable_web_uploads": True,
        assert settings["enable_web_uploads"]
        assert not settings["enable_web_uploads"]
    global_state.set_app_settings(dict(enable_web_uploads=True))
            global_state.set_app_settings(dict(enable_web_uploads=False))
            resp = c.get("/dtale/web-upload", query_string=params)
            response_data = resp.get_json()
            assert not response_data["success"]
            assert response_data["error"] == (
                "Web uploads not enabled! Web uploads are vulnerable to blind server side request forgery, please "
                "only use in trusted environments."
            )
            global_state.set_app_settings(dict(enable_web_uploads=True))

        enable_web_uploads=True,
            enable_web_uploads=True,
            enable_web_uploads=True,