Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions xwiki/CVE-2025-24893/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Arguments
ARG JAVA_VERSION=17
ARG XWIKI_VERSION=17.4.3
# Change at build time to 15.10.10 for a vulnerable version

FROM openjdk:${JAVA_VERSION}-jdk-slim

ARG XWIKI_VERSION
ENV XWIKI_DOWNLOAD_URL=https://nexus.xwiki.org/nexus/content/repositories/releases/org/xwiki/platform/xwiki-platform-distribution-jetty-hsqldb/${XWIKI_VERSION}/xwiki-platform-distribution-jetty-hsqldb-${XWIKI_VERSION}.zip

# Creating non-root user for XWiki
RUN useradd -ms /bin/bash xwiki

# Update and install required packages
RUN apt-get update && \
apt-get install -y wget unzip ca-certificates python3-requests && \
rm -rf /var/lib/apt/lists/*

WORKDIR /opt
ENV DIR_XWIKI=/opt/xwiki

# Downloading XWiki
RUN wget --quiet --show-progress --tries=3 -O xwiki.zip "${XWIKI_DOWNLOAD_URL}" && \
unzip xwiki.zip && \
rm xwiki.zip && \
mv xwiki-platform-distribution-jetty-hsqldb-${XWIKI_VERSION} xwiki && \
chown -R xwiki:xwiki ${DIR_XWIKI}

# Exposing port 8080 for Jetty
EXPOSE 8080

# Upload init scripts
ENV SCRIPT_INIT=xwiki-init.py
COPY ${SCRIPT_INIT} ${DIR_XWIKI}
RUN chmod +x ${DIR_XWIKI}/${SCRIPT_INIT} && \
chown xwiki:xwiki ${DIR_XWIKI}/${SCRIPT_INIT}

# permanentDirectory
# ENV DIR_PERMANENT=/var/lib/xwiki/data
ENV DIR_PERMANENT=${DIR_XWIKI}/data
# RUN mkdir -p ${DIR_PERMANENT} && \
# chown -R xwiki:xwiki ${DIR_PERMANENT}

# If needed to persist data
# VOLUME ["${DIR_PERMANENT}"]

# Using xwiki user now
USER xwiki

# Offline Repository for installing Standard Flavor
ENV DIR_REPOSITORY=${DIR_PERMANENT}/extension/repository
RUN mkdir -p ${DIR_REPOSITORY}
WORKDIR ${DIR_REPOSITORY}

RUN wget --quiet --show-progress --tries=3 -O repository.zip https://nexus.xwiki.org/nexus/content/groups/public/org/xwiki/platform/xwiki-platform-distribution-flavor-xip/${XWIKI_VERSION}/xwiki-platform-distribution-flavor-xip-${XWIKI_VERSION}.xip && \
unzip repository.zip && \
rm repository.zip


WORKDIR ${DIR_XWIKI}

# Starting Jetty + XWiki
# CMD ["./start_xwiki.sh"]

# Automating the installation process
CMD ["/bin/bash", "-c", "/usr/bin/python3 ${DIR_XWIKI}/${SCRIPT_INIT}"]
22 changes: 22 additions & 0 deletions xwiki/CVE-2025-24893/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

To build and start a vulnerable XWiki instance:
```sh
docker build --build-arg XWIKI_VERSION=15.10.10 --build-arg JAVA_VERSION=11 -t xwiki:15.10.10 .
docker run -d -p 8080:8080 --name xwiki -it xwiki:15.10.10
```

To build and start a patched XWiki instance:
```sh
docker build --build-arg XWIKI_VERSION=17.4.3 -t xwiki:17.4.3 .
docker run -d -p 8080:8080 --name xwiki -it xwiki:17.4.3
```

By default, the script xwiki-init.py is executed (see end of Dockerfile) and automates the following installation steps :
- Wait that XWiki / Jetty starts
- Connect to http://<IP>:8080/xwiki
- Click on "Continue"
- Fulfill the form to create the admin account, then click on "Continue" after the success message is displayed
- Select the Flavor "XWiki Standard Flavor". The page may need to be reloaded if it does not display directly
- Click on "Continue" to continue with the installation process
- When XWiki is successfully set up, you will be redirected from `/bin/distribution/XWiki/Distribution` to `/bin/view/Main/`

155 changes: 155 additions & 0 deletions xwiki/CVE-2025-24893/xwiki-init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/python3

import datetime
import random, re, requests
import sys, string, subprocess
import time



# First, launch XWiki
proc_xwiki = subprocess.Popen(['/opt/xwiki/start_xwiki.sh'])
time.sleep(5)


# Defining variables and functions
baseUrl = 'http://127.0.0.1:8080/xwiki'
distributionUrl = '%s/bin/distribution/XWiki/Distribution' %baseUrl

def printLog(msg):
dateStr = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
print('%s [%s] %s' %(dateStr, 'XWiki-Init', msg))

def dieOnError(msg):
printLog('An error occurred while installing XWiki: %s' %msg)
#sys.exit(1)

def check_xwiki_ok():
rep = rs.get(distributionUrl, allow_redirects=False)
return rep.status_code == 302 and 'Location' in rep.headers and '/bin/view/Main/' in rep.headers['Location']

def check_solrsearch_ok():
solrSearchEndUrl = '/bin/view/Main/SolrSearch'
rep = rs.get(baseUrl+solrSearchEndUrl, allow_redirects=False)
return rep.status_code == 302 and 'Location' in rep.headers and solrSearchEndUrl in rep.headers['Location']

def processInstall():
if check_xwiki_ok():
printLog('XWiki already set up')
else:
# Waiting XWiki to be ready
rep = rs.get(distributionUrl)
while '<h1> Distribution Wizard</h1>' not in rep.text:
time.sleep(5)
rep = rs.get(distributionUrl)
printLog('XWiki started')

# Click on the "Continue" button
rep = rs.get(distributionUrl+'?action=COMPLETE_STEP')
if '<h1>Step 1 - Admin user</h1>' not in rep.text:
return dieOnError('expecting admin step')

# Creating an administrator account
admUser = 'admin'
admPasswd = ''.join(random.choices(string.ascii_letters + string.digits, k=30))
printLog('Creating administrator account... [%s:%s]' %(admUser,admPasswd))

rep = rs.post(distributionUrl, data = {
'register_first_name': 'test',
'register_last_name': 'test',
'xwikiname': admUser,
'register_password': admPasswd,
'register2_password': admPasswd,
'register_email': 'test@test.fr',
})

# Admin account created
if 'You are connected with user' not in rep.text:
return dieOnError('expecting admin created')

# Click on the "Continue" button
rep = rs.get(distributionUrl+'?action=COMPLETE_STEP')
if '<h1>Step 2 - Flavor</h1>' not in rep.text:
return dieOnError('expecting flavor step')

# Chosing XWiki Standard Flavor
printLog('Searching for XWiki Standard Flavor...')
i = 0
iMax = 5
while i < iMax:
rep = rs.get(baseUrl+'/bin/get/resources/uicomponents/flavor?xpage=flavor/picker_flavors&namespace=wiki:xwiki')
rjs = rep.json()
if len(rjs)<1 or 'id' not in rjs[0] or 'id' not in rjs[0]['id'] or 'version' not in rjs[0]['id'] or 'value' not in rjs[0]['id']['version'] or 'name' not in rjs[0] or rjs[0]['name']!='XWiki Standard Flavor':
time.sleep(5)
i += 1
continue
break
if i == iMax:
return dieOnError('standard flavor not found')

printLog('Installing Flavor. This may take a while')

installFlavorData = {
'installFlavor': 'Install this flavor',
'fieldName': 'flavor',
'flavor': '%s:::%s' %(rjs[0]['id']['id'], rjs[0]['id']['version']['value']),
}
rep = rs.post(distributionUrl, data = installFlavorData)

if '<button name="extensionAction" type="submit" value="install">' not in rep.text:
return dieOnError('unexpected behavior when selecting standard flavor')


extensionInfo = {
'readOnly': 'false',
'extensionId': rjs[0]['id']['id'],
'extensionVersion': rjs[0]['id']['version']['value'],
'extensionNamespace': 'wiki:xwiki',
}

rep = rs.post(distributionUrl, data = extensionInfo | {'extensionAction': 'install'})
m = re.search(r'data-xwiki-form-token="([^"]+)"', rep.text)
if m is None:
return dieOnError('could not grep the form token when installing extension')
formToken = m.group(1)

for act in ['progress','continue','progress']: # 'showDetails'
#print(act)
rep = rs.post(distributionUrl, data = extensionInfo | {
'extensionAction': act,
'form_token': formToken,
})

while 'class="extension-status">Installed</' not in rep.text:
time.sleep(30)
#rep = rs.get(distributionUrl) # Working with 15.10.10 but not with 9.11.9
rep = rs.post(distributionUrl, data = installFlavorData)

# Click twice on Continue
for i in range(2):
rep = rs.get(distributionUrl+'?action=COMPLETE_STEP')

# Check if wiki is set up
if check_xwiki_ok():
printLog('XWiki installed with success')
else:
printLog('XWiki may not be setup correctly, please check')

# Checking that SolrSearch is available
if not check_solrsearch_ok():
printLog('Warning: SolrSearch module may not be working properly')

# Globally define rs, used to keep requests' session for the whole script
rs = requests.Session()

rs.mount('http://', requests.adapters.HTTPAdapter(max_retries=requests.adapters.Retry(
total = 10,
backoff_factor = 2,
status_forcelist = [ 500, 502, 503, 504 ],
)))

processInstall()

# Stay alive until XWiki ends
proc_xwiki.wait()