Skip to content

Commit

Permalink
XBOW-025-016 - SSRF via HTTP Attachment Addressed (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc authored Feb 16, 2025
1 parent c1151c9 commit 1a35a72
Show file tree
Hide file tree
Showing 6 changed files with 638 additions and 11 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ The use of environment variables allow you to provide over-rides to default sett
| `APPRISE_CONFIG_LOCK` | Locks down your API hosting so that you can no longer delete/update/access stateful information. Your configuration is still referenced when stateful calls are made to `/notify`. The idea of this switch is to allow someone to set their (Apprise) configuration up and then as an added security tactic, they may choose to lock their configuration down (in a read-only state). Those who use the Apprise CLI tool may still do it, however the `--config` (`-c`) switch will not successfully reference this access point anymore. You can however use the `apprise://` plugin without any problem ([see here for more details](https://github.com/caronc/apprise/wiki/Notify_apprise_api)). This defaults to `no` and can however be set to `yes` by simply defining the global variable as such.
| `APPRISE_DENY_SERVICES` | A comma separated set of entries identifying what plugins to deny access to. You only need to identify one schema entry associated with a plugin to in turn disable all of it. Hence, if you wanted to disable the `glib` plugin, you do not need to additionally include `qt` as well since it's included as part of the (`dbus`) package; consequently specifying `qt` would in turn disable the `glib` module as well (another way to accomplish the same task). To exclude/disable more the one upstream service, simply specify additional entries separated by a `,` (comma) or ` ` (space). The `APPRISE_DENY_SERVICES` entries are ignored if the `APPRISE_ALLOW_SERVICES` is identified. By default, this is initialized to `windows, dbus, gnome, macosx, syslog` (blocking local actions from being issued inside of the docker container)
| `APPRISE_ALLOW_SERVICES` | A comma separated set of entries identifying what plugins to allow access to. You may only use alpha-numeric characters as is the restriction of Apprise Schemas (schema://) anyway. To exclusively include more the one upstream service, simply specify additional entries separated by a `,` (comma) or ` ` (space). The `APPRISE_DENY_SERVICES` entries are ignored if the `APPRISE_ALLOW_SERVICES` is identified.
| `APPRISE_ATTACH_ALLOW_URLS` | A comma separated set of entries identifying the HTTP Attach URLs the Apprise API shall always accept. Use wildcards such as `*` and `?` to help construct the URL/Hosts you identify. Use a space and/or a comma to identify more then one entry. By default this is set to `*` (Accept all provided URLs).
| `APPRISE_ATTACH_DENY_URLS` | A comma separated set of entries identifying the HTTP Attach URLs the Apprise API shall always reject. Use wildcards such as `*` and `?` to help construct the URL/Hosts you identify. The `APPRISE_ATTACH_DENY_URLS` is always processed before the `APPRISE_ATTACH_ALLOW_URLS` list. Use a space and/or a comma to identify more then one entry. By default this is set to `127.0.* localhost*`.
By default this
| `SECRET_KEY` | A Django variable acting as a *salt* for most things that require security. This API uses it for the hash sequences when writing the configuration files to disk (`hash` mode only).
| `ALLOWED_HOSTS` | A list of strings representing the host/domain names that this API can serve. This is a security measure to prevent HTTP Host header attacks, which are possible even under many seemingly-safe web server configurations. By default this is set to `*` allowing any host. Use space to delimit more than one host.
| `APPRISE_PLUGIN_PATHS` | Apprise supports the ability to define your own `schema://` definitions and load them. To read more about how you can create your own customizations, check out [this link here](https://github.com/caronc/apprise/wiki/decorator_notify). You may define one or more paths (separated by comma `,`) here. By default the `apprise_api/var/plugin` directory is scanned (which does not include anything). Feel free to set this to an empty string to disable any custom plugin loading.
Expand Down
40 changes: 29 additions & 11 deletions apprise_api/api/tests/test_attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,11 @@ def test(*args, **kwargs):
# Support multi entries
attachment_payload = [
{
'url': 'http://localhost/my.attachment.3',
'url': 'http://myserver/my.attachment.3',
}, {
'url': 'http://localhost/my.attachment.2',
'url': 'http://myserver/my.attachment.2',
}, {
'url': 'http://localhost/my.attachment.1',
'url': 'http://myserver/my.attachment.1',
}
]
result = parse_attachments(attachment_payload, {})
Expand Down Expand Up @@ -352,18 +352,36 @@ def test(*args, **kwargs):

attachment_payload = [
# Request several images
"https://localhost/myotherfile.png",
"https://localhost/myfile.png"
"https://myserver/myotherfile.png",
"https://myserver/myfile.png"
]
result = parse_attachments(attachment_payload, {})
assert isinstance(result, list)
assert len(result) == 2

# A attachment set where our URLs are blocked
attachment_payload = [
# Request several images
"https://localhost.localdomain/myotherfile.png",
"http://localhost/myfile.png",
"http://127.0.0.1/myfile.png",
"https://127.0.0.3/myfile.png",
]
with self.assertRaises(ValueError):
# We have hosts that will be blocked
parse_attachments(attachment_payload, {})

# Test each
for ap in attachment_payload:
with self.assertRaises(ValueError):
# We have hosts that will be blocked
parse_attachments([ap], {})

attachment_payload = [{
# Request several images
'url': "https://localhost/myotherfile.png",
'url': "https://myserver/myotherfile.png",
}, {
'url': "https://localhost/myfile.png"
'url': "https://myserver/myfile.png"
}]
result = parse_attachments(attachment_payload, {})
assert isinstance(result, list)
Expand All @@ -388,17 +406,17 @@ def test_direct_attachment_parsing_nw(self):
# While we have a network in place, we're intentionally requesting
# URLs that do not exist (hopefully they don't anyway) as we want
# this test to fail.
"https://localhost/garbage/abcd1.png",
"https://localhost/garbage/abcd2.png",
"https://myserver/garbage/abcd1.png",
"https://myserver/garbage/abcd2.png",
]
with self.assertRaises(ValueError):
parse_attachments(attachment_payload, {})

# Support url encoding
attachment_payload = [{
'url': "https://localhost/garbage/abcd1.png",
'url': "https://myserver/garbage/abcd1.png",
}, {
'url': "https://localhost/garbage/abcd2.png",
'url': "https://myserver/garbage/abcd2.png",
}]
with self.assertRaises(ValueError):
parse_attachments(attachment_payload, {})
Loading

0 comments on commit 1a35a72

Please sign in to comment.