mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 17:25:16 -04:00
Remove PDF export
As we already decleared in earlier versions, this patch removes PDF export entirely. It's a not acceptable security risk for every CodiMD instance. The current implementation allowed to extract arbitary files from the CodiMD host and therefore leaking secrets from a `/etc/passwd` to CodiMD's own config files and all secrets contained in it. Thanks to Joona for finding this vulnerability in August last year, which lead to an emergency disabling of PDF exports in 1.5.0. Signed-off-by: Sheogorath <sheogorath@shivering-isles.com>
This commit is contained in:
parent
37923d11f8
commit
a2522888b2
18 changed files with 5 additions and 99 deletions
4
app.json
4
app.json
|
@ -127,10 +127,6 @@
|
||||||
"CMD_IMGUR_CLIENTID": {
|
"CMD_IMGUR_CLIENTID": {
|
||||||
"description": "Imgur API client id",
|
"description": "Imgur API client id",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
|
||||||
"CMD_ALLOW_PDF_EXPORT": {
|
|
||||||
"description": "Enable or disable PDF exports",
|
|
||||||
"required": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addons": [
|
"addons": [
|
||||||
|
|
|
@ -24,7 +24,6 @@ to `config.json` before filling in your own details.
|
||||||
|
|
||||||
| variables | example values | description |
|
| variables | example values | description |
|
||||||
| --------- | ------ | ----------- |
|
| --------- | ------ | ----------- |
|
||||||
| `allowPDFExport` | `true` | Whether or not PDF export is offered. |
|
|
||||||
| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
|
| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
|
||||||
| `dbURL` | `mysql://localhost:3306/database` | Set the db in URL style. If set, then the relevant `db` config entries will be overridden. |
|
| `dbURL` | `mysql://localhost:3306/database` | Set the db in URL style. If set, then the relevant `db` config entries will be overridden. |
|
||||||
| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` |
|
| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` |
|
||||||
|
|
|
@ -28,7 +28,6 @@ defaultNotePath can't be set from env-vars
|
||||||
|
|
||||||
| variable | example value | description |
|
| variable | example value | description |
|
||||||
| -------- | ------------- | ----------- |
|
| -------- | ------------- | ----------- |
|
||||||
| `CMD_ALLOW_PDF_EXPORT` | `true` or `false` | Enable or disable PDF exports |
|
|
||||||
| `CMD_CONFIG_FILE` | `/path/to/config.json` | optional override for the path to CodiMD's config file |
|
| `CMD_CONFIG_FILE` | `/path/to/config.json` | optional override for the path to CodiMD's config file |
|
||||||
| `CMD_DB_URL` | `mysql://localhost:3306/database` | Set the db in URL style. If set, then the relevant `db` config entries will be overridden. |
|
| `CMD_DB_URL` | `mysql://localhost:3306/database` | Set the db in URL style. If set, then the relevant `db` config entries will be overridden. |
|
||||||
| `CMD_LOGLEVEL` | `info`, `debug` ... | Defines what kind of logs are provided to stdout. |
|
| `CMD_LOGLEVEL` | `info`, `debug` ... | Defines what kind of logs are provided to stdout. |
|
||||||
|
|
|
@ -13,7 +13,6 @@ You have to replace _\<NOTE\>_ with either the alias or id of a note you want to
|
||||||
| `/new` | `POST` | **Imports some markdown data into a new note.**<br>A random id will be assigned and the content will equal to the body of the received HTTP-request. The `Content-Type: text/markdown` header should be set on this request. |
|
| `/new` | `POST` | **Imports some markdown data into a new note.**<br>A random id will be assigned and the content will equal to the body of the received HTTP-request. The `Content-Type: text/markdown` header should be set on this request. |
|
||||||
| `/new/<ALIAS>` | `POST` | **Imports some markdown data into a new note with a given alias.**<br>This endpoint equals to the above one except that the alias from the url will be assigned to the note if [FreeURL-mode](../configuration-env-vars.md#users-and-privileges) is enabled. |
|
| `/new/<ALIAS>` | `POST` | **Imports some markdown data into a new note with a given alias.**<br>This endpoint equals to the above one except that the alias from the url will be assigned to the note if [FreeURL-mode](../configuration-env-vars.md#users-and-privileges) is enabled. |
|
||||||
| `/<NOTE>/download` or `/s/<SHORT-ID>/download` | `GET` | **Returns the raw markdown content of a note.** |
|
| `/<NOTE>/download` or `/s/<SHORT-ID>/download` | `GET` | **Returns the raw markdown content of a note.** |
|
||||||
| `/<NOTE>/pdf` | `GET` | **Returns a generated pdf version of the note.**<br>If pdf-support is disabled, a HTTP 403 will be returned.<br>_Please note: Currently pdf export is disabled generally because of a security problem with it._ |
|
|
||||||
| `/<NOTE>/publish` | `GET` | **Redirects to the published version of the note.** |
|
| `/<NOTE>/publish` | `GET` | **Redirects to the published version of the note.** |
|
||||||
| `/<NOTE>/slide` | `GET` | **Redirects to the slide-presentation of the note.**<br>This is only useful on notes which are designed to be slides. |
|
| `/<NOTE>/slide` | `GET` | **Redirects to the slide-presentation of the note.**<br>This is only useful on notes which are designed to be slides. |
|
||||||
| `/<NOTE>/info` | `GET` | **Returns metadata about the note.**<br>This includes the title and description of the note as well as the creation date and viewcount. The data is returned as a JSON object. |
|
| `/<NOTE>/info` | `GET` | **Returns metadata about the note.**<br>This includes the title and description of the note as well as the creation date and viewcount. The data is returned as a JSON object. |
|
||||||
|
|
|
@ -89,29 +89,6 @@ paths:
|
||||||
'text/plain':
|
'text/plain':
|
||||||
example: my-note
|
example: my-note
|
||||||
|
|
||||||
/{note}/pdf:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- note
|
|
||||||
summary: Returns a generated pdf version of the note.
|
|
||||||
description: 'If pdf-support is disabled, a HTTP 403 will be returned.<br>_Please note: Currently pdf export is disabled generally because of a security problem with it._'
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: The generated pdf version of the note
|
|
||||||
content:
|
|
||||||
'application/pdf':
|
|
||||||
example: binary
|
|
||||||
404:
|
|
||||||
description: Note does not exist
|
|
||||||
parameters:
|
|
||||||
- name: note
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: The note which should be exported as pdf
|
|
||||||
content:
|
|
||||||
'text/plain':
|
|
||||||
example: my-note
|
|
||||||
|
|
||||||
/{note}/publish:
|
/{note}/publish:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|
|
@ -2,7 +2,7 @@ LinuxServer.io CodiMD Image
|
||||||
===
|
===
|
||||||
[](https://discord.gg/YWrKVTn)[](https://microbadger.com/images/linuxserver/codimd "Get your own version badge on microbadger.com")[](https://microbadger.com/images/linuxserver/codimd "Get your own version badge on microbadger.com")[](https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-codimd/job/master/)[](https://lsio-ci.ams3.digitaloceanspaces.com/linuxserver/codimd/latest/index.html)
|
[](https://discord.gg/YWrKVTn)[](https://microbadger.com/images/linuxserver/codimd "Get your own version badge on microbadger.com")[](https://microbadger.com/images/linuxserver/codimd "Get your own version badge on microbadger.com")[](https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-codimd/job/master/)[](https://lsio-ci.ams3.digitaloceanspaces.com/linuxserver/codimd/latest/index.html)
|
||||||
|
|
||||||
[LinuxServer.io](https://linuxserver.io) have created an Ubuntu-based multi-arch container image for x86-64, arm64 and armhf which supports PDF export from all architectures using [PhantomJS](https://phantomjs.org/).
|
[LinuxServer.io](https://linuxserver.io) have created an Ubuntu-based multi-arch container image for x86-64, arm64 and armhf.
|
||||||
|
|
||||||
- It supports all the environment variables detailed in the [configuration documentation](../configuration-env-vars.md) to modify it according to your needs.
|
- It supports all the environment variables detailed in the [configuration documentation](../configuration-env-vars.md) to modify it according to your needs.
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,6 @@ app.locals.serverURL = config.serverURL
|
||||||
app.locals.sourceURL = config.sourceURL
|
app.locals.sourceURL = config.sourceURL
|
||||||
app.locals.allowAnonymous = config.allowAnonymous
|
app.locals.allowAnonymous = config.allowAnonymous
|
||||||
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
|
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
|
||||||
app.locals.allowPDFExport = config.allowPDFExport
|
|
||||||
app.locals.authProviders = {
|
app.locals.authProviders = {
|
||||||
facebook: config.isFacebookEnable,
|
facebook: config.isFacebookEnable,
|
||||||
twitter: config.isTwitterEnable,
|
twitter: config.isTwitterEnable,
|
||||||
|
|
|
@ -153,7 +153,6 @@ module.exports = {
|
||||||
email: true,
|
email: true,
|
||||||
allowEmailRegister: true,
|
allowEmailRegister: true,
|
||||||
allowGravatar: true,
|
allowGravatar: true,
|
||||||
allowPDFExport: true,
|
|
||||||
openID: false,
|
openID: false,
|
||||||
// linkifyHeaderStyle - How is a header text converted into a link id.
|
// linkifyHeaderStyle - How is a header text converted into a link id.
|
||||||
// Header Example: "3.1. Good Morning my Friend! - Do you have 5$?"
|
// Header Example: "3.1. Good Morning my Friend! - Do you have 5$?"
|
||||||
|
|
|
@ -129,7 +129,6 @@ module.exports = {
|
||||||
email: toBooleanConfig(process.env.CMD_EMAIL),
|
email: toBooleanConfig(process.env.CMD_EMAIL),
|
||||||
allowEmailRegister: toBooleanConfig(process.env.CMD_ALLOW_EMAIL_REGISTER),
|
allowEmailRegister: toBooleanConfig(process.env.CMD_ALLOW_EMAIL_REGISTER),
|
||||||
allowGravatar: toBooleanConfig(process.env.CMD_ALLOW_GRAVATAR),
|
allowGravatar: toBooleanConfig(process.env.CMD_ALLOW_GRAVATAR),
|
||||||
allowPDFExport: toBooleanConfig(process.env.CMD_ALLOW_PDF_EXPORT),
|
|
||||||
openID: toBooleanConfig(process.env.CMD_OPENID),
|
openID: toBooleanConfig(process.env.CMD_OPENID),
|
||||||
linkifyHeaderStyle: process.env.CMD_LINKIFY_HEADER_STYLE
|
linkifyHeaderStyle: process.env.CMD_LINKIFY_HEADER_STYLE
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,5 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
email: toBooleanConfig(process.env.HMD_EMAIL),
|
email: toBooleanConfig(process.env.HMD_EMAIL),
|
||||||
allowEmailRegister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER),
|
allowEmailRegister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER)
|
||||||
allowPDFExport: toBooleanConfig(process.env.HMD_ALLOW_PDF_EXPORT)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,6 @@ config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
|
||||||
config.isLDAPEnable = config.ldap.url
|
config.isLDAPEnable = config.ldap.url
|
||||||
config.isSAMLEnable = config.saml.idpSsoUrl
|
config.isSAMLEnable = config.saml.idpSsoUrl
|
||||||
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
|
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
|
||||||
config.isPDFExportEnable = config.allowPDFExport
|
|
||||||
|
|
||||||
// Check gitlab api version
|
// Check gitlab api version
|
||||||
if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') {
|
if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') {
|
||||||
|
@ -188,12 +187,6 @@ switch (config.imageUploadType) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable PDF export due to security issue
|
|
||||||
if (config.allowPDFExport) {
|
|
||||||
config.allowPDFExport = false
|
|
||||||
logger.warn('PDF export was disabled for this release to mitigate a critical security issue. This feature will hopefully become available again in future releases.')
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate correct path
|
// generate correct path
|
||||||
config.sslCAPath.forEach(function (capath, i, array) {
|
config.sslCAPath.forEach(function (capath, i, array) {
|
||||||
array[i] = path.resolve(appRootPath, capath)
|
array[i] = path.resolve(appRootPath, capath)
|
||||||
|
|
|
@ -37,6 +37,5 @@ module.exports = {
|
||||||
// document
|
// document
|
||||||
documentmaxlength: undefined,
|
documentmaxlength: undefined,
|
||||||
imageuploadtype: undefined,
|
imageuploadtype: undefined,
|
||||||
allowemailregister: undefined,
|
allowemailregister: undefined
|
||||||
allowpdfexport: undefined
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ const config = require('../../config')
|
||||||
const errors = require('../../errors')
|
const errors = require('../../errors')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const shortId = require('shortid')
|
const shortId = require('shortid')
|
||||||
const markdownpdf = require('markdown-pdf')
|
|
||||||
const moment = require('moment')
|
const moment = require('moment')
|
||||||
const querystring = require('querystring')
|
const querystring = require('querystring')
|
||||||
|
|
||||||
|
@ -33,37 +32,6 @@ exports.getInfo = function getInfo (req, res, note) {
|
||||||
res.send(data)
|
res.send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createPDF = function createPDF (req, res, note) {
|
|
||||||
const url = config.serverURL || 'http://' + req.get('host')
|
|
||||||
const body = note.content
|
|
||||||
const extracted = models.Note.extractMeta(body)
|
|
||||||
let content = extracted.markdown
|
|
||||||
const title = models.Note.decodeTitle(note.title)
|
|
||||||
|
|
||||||
if (!fs.existsSync(config.tmpPath)) {
|
|
||||||
fs.mkdirSync(config.tmpPath)
|
|
||||||
}
|
|
||||||
const path = config.tmpPath + '/' + Date.now() + '.pdf'
|
|
||||||
content = content.replace(/\]\(\//g, '](' + url + '/')
|
|
||||||
markdownpdf().from.string(content).to(path, function () {
|
|
||||||
if (!fs.existsSync(path)) {
|
|
||||||
logger.error('PDF seems to not be generated as expected. File doesn\'t exist: ' + path)
|
|
||||||
return errors.errorInternalError(res)
|
|
||||||
}
|
|
||||||
const stream = fs.createReadStream(path)
|
|
||||||
let filename = title
|
|
||||||
// Be careful of special characters
|
|
||||||
filename = encodeURIComponent(filename)
|
|
||||||
// Ideally this should strip them
|
|
||||||
res.setHeader('Content-disposition', 'attachment; filename="' + filename + '.pdf"')
|
|
||||||
res.setHeader('Cache-Control', 'private')
|
|
||||||
res.setHeader('Content-Type', 'application/pdf; charset=UTF-8')
|
|
||||||
res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
|
|
||||||
stream.pipe(res)
|
|
||||||
fs.unlinkSync(path)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.createGist = function createGist (req, res, note) {
|
exports.createGist = function createGist (req, res, note) {
|
||||||
const data = {
|
const data = {
|
||||||
client_id: config.github.clientID,
|
client_id: config.github.clientID,
|
||||||
|
|
|
@ -111,14 +111,6 @@ export module NoteController {
|
||||||
case 'info':
|
case 'info':
|
||||||
noteActions.getInfo(req, res, note);
|
noteActions.getInfo(req, res, note);
|
||||||
break;
|
break;
|
||||||
case 'pdf':
|
|
||||||
if (config.allowPDFExport) {
|
|
||||||
noteActions.createPDF(req, res, note)
|
|
||||||
} else {
|
|
||||||
logger.error('PDF export failed: Disabled by config. Set "allowPDFExport: true" to enable. Check the documentation for details');
|
|
||||||
errors.errorForbidden(res)
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'gist':
|
case 'gist':
|
||||||
noteActions.createGist(req, res, note);
|
noteActions.createGist(req, res, note);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -79,7 +79,6 @@
|
||||||
"markdown-it-regexp": "^0.4.0",
|
"markdown-it-regexp": "^0.4.0",
|
||||||
"markdown-it-sub": "^1.0.0",
|
"markdown-it-sub": "^1.0.0",
|
||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"markdown-pdf": "^10.0.0",
|
|
||||||
"mathjax": "~2.7.6",
|
"mathjax": "~2.7.6",
|
||||||
"mermaid": "~8.4.6",
|
"mermaid": "~8.4.6",
|
||||||
"meta-marked": "git+https://github.com/codimd/meta-marked#semver:^0.4.5",
|
"meta-marked": "git+https://github.com/codimd/meta-marked#semver:^0.4.5",
|
||||||
|
|
|
@ -941,8 +941,6 @@ ui.toolbar.download.rawhtml.click(function (e) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
exportToRawHTML(ui.area.markdown)
|
exportToRawHTML(ui.area.markdown)
|
||||||
})
|
})
|
||||||
// pdf
|
|
||||||
ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf')
|
|
||||||
// export to dropbox
|
// export to dropbox
|
||||||
ui.toolbar.export.dropbox.click(function () {
|
ui.toolbar.export.dropbox.click(function () {
|
||||||
var filename = renderFilename(ui.area.markdown) + '.md'
|
var filename = renderFilename(ui.area.markdown) + '.md'
|
||||||
|
|
|
@ -17,8 +17,7 @@ export const getUIElements = () => ({
|
||||||
download: {
|
download: {
|
||||||
markdown: $('.ui-download-markdown'),
|
markdown: $('.ui-download-markdown'),
|
||||||
html: $('.ui-download-html'),
|
html: $('.ui-download-html'),
|
||||||
rawhtml: $('.ui-download-raw-html'),
|
rawhtml: $('.ui-download-raw-html')
|
||||||
pdf: $('.ui-download-pdf-beta')
|
|
||||||
},
|
},
|
||||||
export: {
|
export: {
|
||||||
dropbox: $('.ui-save-dropbox'),
|
dropbox: $('.ui-save-dropbox'),
|
||||||
|
|
|
@ -63,10 +63,6 @@
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-download-raw-html" tabindex="-1" href="#" target="_self"><i class="fa fa-file-code-o fa-fw"></i> <%= __('Raw HTML') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-download-raw-html" tabindex="-1" href="#" target="_self"><i class="fa fa-file-code-o fa-fw"></i> <%= __('Raw HTML') %></a>
|
||||||
</li>
|
</li>
|
||||||
<% if(typeof allowPDFExport !== 'undefined' && allowPDFExport) {%>
|
|
||||||
<li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
|
|
||||||
</li>
|
|
||||||
<% } %>
|
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-help" href="#" data-toggle="modal" data-target=".help-modal"><i class="fa fa-question-circle fa-fw"></i> Help</a>
|
<li role="presentation"><a role="menuitem" class="ui-help" href="#" data-toggle="modal" data-target=".help-modal"><i class="fa fa-question-circle fa-fw"></i> Help</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -162,10 +158,6 @@
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-download-raw-html" tabindex="-1" href="#" target="_self"><i class="fa fa-file-code-o fa-fw"></i> <%= __('Raw HTML') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-download-raw-html" tabindex="-1" href="#" target="_self"><i class="fa fa-file-code-o fa-fw"></i> <%= __('Raw HTML') %></a>
|
||||||
</li>
|
</li>
|
||||||
<% if(typeof allowPDFExport !== 'undefined' && allowPDFExport) {%>
|
|
||||||
<li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
|
|
||||||
</li>
|
|
||||||
<% } %>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue