fomkin / korolev

Single Page Applications running on the server side.
Apache License 2.0
579 stars 50 forks source link

innerHTML content can break korolev #339

Closed Zhen-hao closed 3 years ago

Zhen-hao commented 3 years ago

I'm using this trick to display an embedded HTML generated by user markdown input.

delay(0.01.seconds) { access =>
                access.evalJs(js"""$renderUserInput.innerHTML = `${renderedHtml}`""").map(_ => ())
              },

but on the following input (copied from stackoverflow)

<pre><code>

@Configuration
@EnableReactiveCassandraRepositories(basePackages = "com.cassandra.repository.a”, reactiveCassandraTemplateRef = “keyspaceCassandraTemplateA”)
public class AkeyspaceCassandraConfiguration extends BaseCassandraConfiguration {

    @Value("${spring.data.cassandra.keyspace-name.keyspaceA}”)
    private String keyspace;

    @NonNull
    @Override
    public String getKeyspaceName() {
        return keyspace;
    }

    @NonNull
    @Override
    public CqlSessionFactoryBean cassandraSession() {
        final CqlSessionFactoryBean cqlSessionFactoryBean = super.cassandraSession();
        cqlSessionFactoryBean.setKeyspaceName(keyspace);
        return cqlSessionFactoryBean;
    }

    @Bean(“keyspaceCassandraTemplateA”)
    public ReactiveCassandraTemplate reactiveCassandraTemplate() {
        final ReactiveSession reactiveSession = new DefaultBridgedReactiveSession(cassandraSession().getObject());
        return new ReactiveCassandraTemplate(reactiveSession);
    }
@Configuration
@EnableReactiveCassandraRepositories(basePackages = "com.cassandra.repository.B”, reactiveCassandraTemplateRef = “keyspaceCassandraTemplateB”)
public class BKeyspaceCassandraConfiguration extends BaseCassandraConfiguration {

</code></pre>

Korolev throws an exception

[info] korolev.internal.Frontend$ClientSideException: ReferenceError: spring is not defined
[info]  at korolev.internal.Frontend.$anonfun$new$1(Frontend.scala:286)
[info]  at korolev.effect.Stream.$anonfun$foreach$1(Stream.scala:257)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:433)
[info]  at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:67)
[info]  at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.linkRootOf(Promise.scala:311)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:434)
[info]  at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:67)
[info]  at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467)
[info]  at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:67)
[info]  at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467)
[info]  at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:67)
[info]  at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467)
[info]  at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:67)
[info]  at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:242)
[info]  at scala.concurrent.Promise.complete(Promise.scala:57)
[info]  at scala.concurrent.Promise.complete$(Promise.scala:56)
[info]  at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:104)
[info]  at korolev.effect.Effect$FutureEffect.$anonfun$promise$1(Effect.scala:99)
[info]  at korolev.effect.Effect$FutureEffect.$anonfun$promise$1$adapted(Effect.scala:99)
[info]  at korolev.akka.util.KorolevStreamSubscriber.onNext(KorolevStreamSubscriber.scala:41)
[info]  at akka.stream.impl.VirtualProcessor.rec$5(StreamLayout.scala:341)
[info]  at akka.stream.impl.VirtualProcessor.onNext(StreamLayout.scala:370)
[info]  at akka.stream.impl.ReactiveStreamsCompliance$.tryOnNext(ReactiveStreamsCompliance.scala:100)
[info]  at akka.stream.impl.fusing.ActorGraphInterpreter$ActorOutputBoundary.onNext(ActorGraphInterpreter.scala:382)
[info]  at akka.stream.impl.fusing.ActorGraphInterpreter$ActorOutputBoundary.onPush(ActorGraphInterpreter.scala:407)
[info]  at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
[info]  at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:423)
[info]  at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:625)
[info]  at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:502)
[info]  at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:600)
[info]  at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:769)
[info]  at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$shortCircuitBatch(ActorGraphInterpreter.scala:759)
[info]  at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive$1.applyOrElse(ActorGraphInterpreter.scala:785)
[info]  at akka.actor.Actor.aroundReceive(Actor.scala:537)
[info]  at akka.actor.Actor.aroundReceive$(Actor.scala:535)
[info]  at akka.stream.impl.fusing.ActorGraphInterpreter.aroundReceive(ActorGraphInterpreter.scala:691)
[info]  at akka.actor.ActorCell.receiveMessage(ActorCell.scala:577)
[info]  at akka.actor.ActorCell.invoke(ActorCell.scala:547)
[info]  at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
[info]  at akka.dispatch.Mailbox.run(Mailbox.scala:231)
[info]  at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
[info]  at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
[info]  at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
[info]  at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
[info]  at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
[info]  at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
fomkin commented 3 years ago

Yes, innerHTML shoul be error prone because Korolev doesn't manage inserted DOM. However stack trace you show looks anxious. Looks like spring had been interpreted as JavaScipt for some reason. It can lead to XSS vulnerability.

Zhen-hao commented 3 years ago

is there a safer way to embed external HTML?

fomkin commented 3 years ago

Convert your HTML to levsha.Node of course. Look at this example https://gist.github.com/fomkin/f3709afdf53dd0a9e06e07eb16b979f2. This code is not production ready, but shows how to do such things.

Zhen-hao commented 3 years ago

@fomkin I'm playing with the approach you suggested. it seems that the result node can get stuck and not receive updates even though the content has changed. it happens when my state is loaded from a path (hence resolved by the router). it works if the state is loaded from an event (hence via the render).

fomkin commented 3 years ago

About the issue:

  1. js"" interpolator doesn't escape special characters. The renderedHtml string contains them. I think it leads to the error.
  2. Approach this converting your HTML to levsha.Node can't lead to problems you described. There is no difference in rendering between state from path and state from event.