【发布时间】:2019-10-21 03:21:33
【问题描述】:
用户正在向我们的 React 应用程序提供一个字符串,并且它正在显示给其他用户。我想搜索一些字符,并用一些 HTML 替换它们,就像我要搜索“特殊”这个词一样,我会把它变成:
My <span class="special-formatting">special</span> word in a user string
以前我执行此替换,然后使用危险的SetInnerHTML 将结果插入到 DOM 中。这当然给我带来了用户能够在应用程序中键入和输入他们喜欢的任何 HTML/Javascript 并将其呈现给所有人查看的问题。
我尝试将 HTML 字符转义为它们的实体,但危险的 SetInnerHTML 似乎正确地呈现 HTML 实体,而不是实际的字符串。 (编辑:见下文,这是实际的解决方案)
有什么方法可以将他们的消息转换为纯字符串,仍然保留那些特殊字符的显示,而且还可以将我自己的 HTML 插入到字符串中?尽量避免在将每个字符串插入 DOM 后运行脚本。
以下是有关当前流程的更多信息。所有示例都经过优化,只显示相关代码。
用户文本通过这个函数提交到数据库:
handleSubmit(event) {
event.preventDefault();
var messageText = this.state.messageValue;
//bold font is missing some common characters, fake way of making the normal font look bold
if (this.state.bold == true) {
messageText = messageText.replace(/\'/g, "<span class='bold-apostrophe'>'</span>");
messageText = messageText.replace(/\"/g, "<span class='bold-quote'>"</span>");
messageText = messageText.replace(/\?/g, "<span class='bold-question'>?</span>");
messageText = messageText.replace(/\*/g, "<span class='bold-asterisk'>*</span>");
messageText = messageText.replace(/\+/g, "<span class='bold-plus'>+</span>");
messageText = messageText.replace(/\./g, "<span class='bold-period'>.</span>");
messageText = messageText.replace(/\,/g, "<span class='bold-comma'>,</span>");
}
Messages.insert({
text: messageText,
createdAt: new Date(),
userId: user._id,
bold: this.state.bold,
});
}
所以,我进行了替换没有问题,但是此时,messageText 字符串仍可能包含不需要的用户输入 HTML 代码。
然后,我们带有消息列表的主应用程序会尝试呈现所有用户消息:
render() {
return (
<div ref="messagesList">
{this.renderMessages()}
</div>
);
}
renderMessages() {
return [].concat(this.props.messages).reverse().map((message) => {
return <Message
key={message._id}
message={message} />;
}
});
}
在 Message.jsx 中,我对消息字符串进行最后润色(某些更改我不想保存到消息数据库中)并将其插入到要返回的元素中:
export default class Message extends React.Component {
render() {
var processedMessageText = this.props.message.text;
//another find and replace to insert images for :image_name: strings, similar to how Discord inputs its emoji
processedMessageText = processedMessageText.replace(/:([\w]+):/g, function (text) {
text = text.replace(/:/g, "");
if (text.indexOf("_s") !== -1) {
text = text.replace(/_s/g, "");
text = "<img class='small-smiley' src='/smileys/small/" + text + ".png'>";
return text;
}
else {
text = "<img class='smiley' src='/smileys/" + text + ".png'>";
return text;
}
});
return (
<div>
<div className='username'>{this.props.message.username}: </div>
<div className='text' dangerouslySetInnerHTML={{ __html: processedMessageText }}></div>
</div>
);
}
}
同样,如果用户在他们的输入字符串中包含恶意 HTML,它将遍历所有这些并输出到消息列表,这真的很糟糕。我希望有某种方法可以将这些所需的 HTML 插入到他们的字符串中,同时也不会将他们可能输入的 HTML 呈现为实际的 HTML。我还想显示 HTML 中常用的字符,比如尖括号 (),所以我想避免直接剥离它们输入的常见 HTML 字符字符串。
由于接受的答案没有太多细节,我将在这里发布我最终所做的事情。在添加我自己的 HTML 并将其呈现为 HTML 元素的内容之前,我对 OWASP 建议的字符进行了 HTML 编码。我想避免使用另一个库,所以我这样做了:
messageText = messageText.replace(/\&/g, "&");
messageText = messageText.replace(/</g, "<");
messageText = messageText.replace(/>/g, ">");
messageText = messageText.replace(/\//g, "/");
messageText = messageText.replace(/\'/g, "'");
messageText = messageText.replace(/\"/g, """);
这样做后,我不再能够插入任何恶意内容,并且使用来自 OWASP 的各种测试字符串进行了测试,没有问题。
【问题讨论】:
-
来自服务器的字符串中是否有 html 标记?如果不是,为什么不能将字符串拆分为单词并有条件地呈现匹配的单词。
-
我没有任何问题有条件地呈现匹配的单词。我的问题是用户可以输入“hello”,由于我使用的是危险的SetInnerHTML,它实际上会呈现该 HTML。
-
我的意思是,如果您不想维护来自服务器的任何 html 标签,您可以使用普通的旧反应。
return response.split(" ").map((w) => w === 'special' ? <span className='special-formatting'>{w}</span> ? w);` .这对 XSS 来说是安全的 -
嗯,当我们渲染消息时,它是一个映射消息并将它们作为相当大的组件返回的函数。这些组件的缩写形式是:
<div className='text' dangerouslySetInnerHTML={{ __html: processedMessageText }}></div>其中 processesMessageText 是我拉入其自己的变量并对其执行各种替换的用户字符串。这又给我留下了一个字符串,我可以将其呈现为带有潜在危险代码的 HTML,也可以呈现为没有 HTML 的纯字符串 -
添加了我们完整工作流程的示例。鉴于当前的结构,我不确定我在哪里实现了类似的东西。
标签: javascript html reactjs