Search code examples
javaexceptionhyperlinkwicketreplay

Wicket link replay attack


I have a ListView composed by different fragment which contains text and a link (inside another fragment). The link is visible depending on the state of the listview model.

For simplicity let's say the link is visible depending on a boolean field of the listview model, if it's true is visible, invisible otherwise.

At first the link is visible, I copy the link location (encrypted), I wait for my model to change (i.e. boolean to false) and after I refresh the page the link is gone. (correct!)

If I try to give the URL (copied before) back in the browser I receive a WicketRuntimeException telling me that the listener for this link was not found.

To be more complete the link is inside a fragment:

<wicket:fragment wicket:id="reservationRatingFragment">
    <li>
        <div>
            <img src="/img/good.png" />
        </div>
        <p>
            <a wicket:id="ratingGoodLink" href="#"> <wicket:message
                    key="messaging.reservation.rating.good" />
            </a>
        </p>
    </li>
</wicket:fragment>

And when I say invisible I mean that I set the markup container of the fragment as .setVisible(false);

Why is this happening? I'm supposing that if I recall a link which is not visible anymore the framework should just skip it and refresh the page I'm currently on (or redirect me to the base page).

If for example I copy the link and I change BasePage (go to the homepage for example), the exception still occurs when I'm recalling the copied URL.

EDITED:

In the first fragment:

WebMarkupContainer msgRatingContainer = new WebMarkupContainer("messageRatingContainer") {
            private static final long serialVersionUID = 1L;

            @Override
            public void onConfigure() {
                setVisible(message.getType() == MessageType.RATING);
            }
        };

if (msgRatingContainer.isVisible()) {
            if (message.getType() == MessageType.RATING) {
                msgRatingContainer.add(new ReservationRatingFragment("messageRatingSection",
                        "reservationRatingFragment", this, item, message));
}

The nested fragment (ReservationRatingFragment):

public ReservationRatingFragment(String id, String markupId,MarkupContainer markupContainer, Item item, Message msg) {
        super(id, markupId, markupContainer, new Model<Message>(msg));
        /* Avoid render container */
        setRenderBodyOnly(true);

        /* Load button components */
        Link<Void> ratingGoodLink = new Link<Void>("ratingGoodLink"){
            private static final long serialVersionUID = 1L;

            @Override
            public void onClick() {
                processRating(ReservationEvaluationResult.GOOD);
            }   
        };
        add(ratingGoodLink);

        Link<Void> ratingBadLink = new Link<Void>("ratingBadLink"){
            private static final long serialVersionUID = 1L;
            @Override
            public void onClick() {
                processRating(ReservationEvaluationResult.BAD);
            }   
        };
        add(ratingBadLink);
    }

Markup for both fragments:

<wicket:fragment wicket:id="messageFragment">
    Some content...
    <!-- Here goes my fragment with link -->
    <ul wicket:id="messageRatingContainer">
        <div wicket:id="messageRatingSection"></div>
    </ul>

    <wicket:fragment wicket:id="reservationRatingFragment">
        <li><div>
                <img src="/img/messaging/good.png" />
            </div>
            <p>
                <a wicket:id="ratingGoodLink" href="#"> <wicket:message
                        key="messaging.reservation.rating.good" />
                </a>
            </p></li>
        <li><div>
                <img src="/img/messaging/bad.png" />
            </div>
            <p>
                <a wicket:id="ratingBadLink" href="#"> <wicket:message
                        key="messaging.reservation.rating.bad" />
                </a>
            </p></li>
    </wicket:fragment>
</wicket:fragment>

EDITED: The processRating just perform a call to a controller (which handle the change in the backend). In the controller I check for the replay attack (if this action is already performed) and if so I throw a runtime exception that lead the user to a warning page (You already rated this message). The fact is, in this case it don't get to this point, since the link is not available it doesn't call the controller and it just throw the InvalidUrlException since the link is not visible.

Wicket version: 1.4.19

Thanks


Solution

  • The only viable solution I found was to extend the RequestCycle and override the onRuntimeException method this way:

    @Override
    public Page onRuntimeException(Page page, RuntimeException e) {     
        if(e instanceof InvalidUrlException) {
            return new HomePage();
        } else {
            return super.onRuntimeException(page, e);
        }
    }