VYPR
Medium severity5.4NVD Advisory· Published May 13, 2026· Updated May 14, 2026

CVE-2026-45228

CVE-2026-45228

Description

Quark Drive before 0.8.5 contains a stored cross-site scripting vulnerability in the System Configuration page where the template renders push_config key names using Vue.js's v-html directive without escaping. Authenticated attackers can inject HTML or JavaScript payloads as key names through the POST /update endpoint, which are persisted to disk and executed in the browsers of all authenticated users accessing the System Configuration tab, allowing session cookie exfiltration and arbitrary authenticated actions.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Quark Drive before 0.8.5 has a stored XSS in the System Configuration page via unescaped v-html rendering of push_config key names, allowing authenticated attackers to execute arbitrary JavaScript in other users' browsers.

Vulnerability

Quark Drive versions prior to 0.8.5 contain a stored cross-site scripting (XSS) vulnerability in the System Configuration page. The template uses Vue.js's v-html directive to render push_config key names without escaping, allowing injection of arbitrary HTML or JavaScript. The vulnerability affects the index.html file where v-html is used on user-controlled key names from push_config and plugin objects [1][3]. Version 0.8.5 fixes this by replacing v-html with proper text interpolation using {{ }}.

Exploitation

An attacker must have authenticated access to the Quark Drive web interface. The attacker can send a crafted POST request to the /update endpoint with a malicious push_config key name containing HTML or JavaScript payloads (e.g., ``). The payload is stored server-side and will execute in the browser of any authenticated user who views the System Configuration page, including the attacker's own session for validation [3].

Impact

Successful exploitation allows an authenticated attacker to execute arbitrary JavaScript in the context of other authenticated users' browsers. This can lead to session cookie exfiltration, enabling the attacker to impersonate victims and perform arbitrary authenticated actions, such as modifying system configuration or accessing sensitive data. The impact is limited to authenticated users but can lead to privilege escalation within the application [3].

Mitigation

The vulnerability is fixed in version 0.8.5 of Quark Drive, released on an unspecified date [2]. The fix, shown in commit 8436e2821988637ed7bfc5562544d089e6b29478, replaces all v-html bindings with safe text interpolation using {{ }} in index.html [1]. Users should upgrade to version 0.8.5 or later. No workarounds are documented, and there is no evidence this CVE is listed in CISA's Known Exploited Vulnerabilities (KEV) catalog.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
8436e2821988

security(index.html): 防止潜在的 XSS 问题

https://github.com/Cp0204/quark-auto-saveCp0204Apr 17, 2026via nvd-ref
1 file changed · +15 15
  • app/templates/index.html+15 15 modified
    @@ -115,7 +115,7 @@ <h2 style="display: inline-block;"><i class="bi bi-bell"></i> 通知</h2>
                 </div>
                 <div v-for="(value, key) in formData.push_config" :key="key" class="input-group mb-2">
                   <div class="input-group-prepend">
    -                <span class="input-group-text" v-html="key"></span>
    +                <span class="input-group-text">{{ key }}</span>
                   </div>
                   <div class="input-group-prepend" v-if="(key == 'DEER_KEY' || key == 'PUSH_KEY')">
                     <a type="button" class="btn btn-warning" target="_blank" href="https://sct.ftqq.com/r/13249" title="Server酱推荐计划"><i class="bi bi-award"></i></a>
    @@ -138,17 +138,17 @@ <h2 style="display: inline-block;"><i class="bi bi-plug"></i> 插件</h2>
                   <div class="form-group row mb-0" style="display:flex; align-items:center;">
                     <div data-toggle="collapse" :data-target="'#collapse_'+pluginName" aria-expanded="true" :aria-controls="'collapse_'+pluginName">
                       <div class="btn btn-block text-left">
    -                    <i class="bi bi-caret-right-fill"></i> <span v-html="`${pluginName}`"></span>
    +                    <i class="bi bi-caret-right-fill"></i> <span>{{ pluginName }}</span>
                       </div>
                     </div>
                   </div>
                   <div class="collapse ml-3 mt-2" :id="'collapse_'+pluginName">
                     <template v-for="(value, key) in plugin" :key="key">
                       <div v-if="key.startsWith('tips_')">
    -                    <div class="alert alert-warning" role="alert" v-html="value"></div>
    +                    <div class="alert alert-warning" role="alert">{{ value }}</div>
                       </div>
                       <div v-else class="form-group row">
    -                    <label class="col-sm-2 col-form-label" v-html="key"></label>
    +                    <label class="col-sm-2 col-form-label">{{ key }}</label>
                         <div class="col-sm-10">
                           <input :type="key.includes('password') || key.includes('token') || key.includes('secret') ? 'password' : 'text'" v-model="formData.plugins[pluginName][key]" class="form-control">
                         </div>
    @@ -296,7 +296,7 @@ <h2>任务列表</h2>
                       <label class="col-form-label col-md-3">路径筛选</label>
                       <div class="input-group col-md-9">
                         <select class="form-control" v-model="taskDirSelected">
    -                      <option v-for="(dir, index) in taskDirs" :value="dir" v-html="dir"></option>
    +                      <option v-for="(dir, index) in taskDirs" :value="dir">{{ dir }}</option>
                         </select>
                         <div class="input-group-append">
                           <button type="button" class="btn btn-outline-secondary" @click="clearData('taskDirSelected')"><i class="bi bi-x-lg"></i></button>
    @@ -311,7 +311,7 @@ <h2>任务列表</h2>
                     <div class="form-group row" style="align-items:center">
                       <div class="col pl-0" data-toggle="collapse" :data-target="'#collapse_'+index" aria-expanded="true" :aria-controls="'collapse_'+index">
                         <div class="btn btn-block text-left">
    -                      <i class="bi bi-caret-right-fill"></i> #<span v-html="`${index+1}: ${task.taskname}`"></span>
    +                      <i class="bi bi-caret-right-fill"></i> #<span>{{ index+1 }}: {{ task.taskname }}</span>
                         </div>
                       </div>
                       <div class="col-auto">
    @@ -322,7 +322,7 @@ <h2>任务列表</h2>
                       </div>
                     </div>
                     <div class="collapse ml-3" :id="'collapse_'+index">
    -                  <div class="alert alert-warning" role="alert" v-if="task.shareurl_ban" v-html="task.shareurl_ban"></div>
    +                  <div class="alert alert-warning" role="alert" v-if="task.shareurl_ban">{{ task.shareurl_ban }}</div>
                       <div class="form-group row">
                         <label class="col-sm-2 col-form-label">任务名称</label>
                         <div class="col-sm-10">
    @@ -331,7 +331,7 @@ <h2>任务列表</h2>
                             <div class="dropdown-menu show task-suggestions" v-if="smart_param.showSuggestions && smart_param.taskSuggestions.success && smart_param.index === index">
                               <div class="dropdown-item text-muted text-center" style="font-size:12px;">{{ smart_param.taskSuggestions.message ? smart_param.taskSuggestions.message : smart_param.taskSuggestions.data.length ? `以下资源来自网络搜索,请自行辨识,如有侵权请联系资源方` : "未搜索到资源" }}</div>
                               <div v-for="suggestion in smart_param.taskSuggestions.data" :key="suggestion.taskname" class="dropdown-item cursor-pointer" @click.prevent="selectSuggestion(index, suggestion)" style="font-size: 12px;" :title="suggestion.content">
    -                            <span v-html="suggestion.verify ? '✅': '❔'"></span> {{ suggestion.taskname }}
    +                            <span>{{ suggestion.verify ? '✅': '❔' }}</span> {{ suggestion.taskname }}
                                 <small class="text-muted">
                                   <a :href="suggestion.shareurl" target="_blank" @click.stop>{{ suggestion.shareurl }}</a>
                                 </small>
    @@ -394,7 +394,7 @@ <h2>任务列表</h2>
                             </div>
                           </div>
                           <datalist id="magicRegex">
    -                        <option v-for="(value, key) in formData.magic_regex" :key="key" :value="`${key}`" v-html="`${value.pattern.replace('<', '<\u200B')} → ${value.replace}`"></option>
    +                        <option v-for="(value, key) in formData.magic_regex" :key="key" :value="`${key}`">{{ value.pattern }} → {{ value.replace }}</option>
                           </datalist>
                         </div>
                       </div>
    @@ -482,7 +482,7 @@ <h5 class="modal-title"><b>运行日志</b>
                 </button>
               </div>
               <div class="modal-body">
    -            <pre v-html="run_log"></pre>
    +            <pre>{{ run_log }}</pre>
               </div>
             </div>
           </div>
    @@ -532,14 +532,14 @@ <h5 class="modal-title">
                     </div>
                   </div>
                 </div>
    -            <div class="alert alert-warning" v-if="fileSelect.error" v-html="fileSelect.error"></div>
    +            <div class="alert alert-warning" v-if="fileSelect.error">{{ fileSelect.error }}</div>
                 <div v-else>
                   <!-- 正则处理表达式 -->
                   <div class="mb-3" v-if="fileSelect.previewRegex && fileSelect.index<this.formData.tasklist.length">
    -                <div><b>匹配表达式:</b><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span>
    +                <div><b>匹配表达式:</b><span class="badge badge-info">{{ formData.tasklist[fileSelect.index].pattern }}</span>
                       <span class="badge badge-info" v-if="formData.tasklist[fileSelect.index].pattern in formData.magic_regex">{{ formData.magic_regex[formData.tasklist[fileSelect.index].pattern].pattern }}</span>
                     </div>
    -                <div><b>替换表达式:</b><span class="badge badge-info" v-if="formData.tasklist[fileSelect.index].replace" v-html="formData.tasklist[fileSelect.index].replace"></span>
    +                <div><b>替换表达式:</b><span class="badge badge-info" v-if="formData.tasklist[fileSelect.index].replace">{{ formData.tasklist[fileSelect.index].replace }}</span>
                       <span class="badge badge-info" v-else-if="formData.tasklist[fileSelect.index].pattern in formData.magic_regex">{{ formData.magic_regex[formData.tasklist[fileSelect.index].pattern].replace }}</span>
                     </div>
                   </div>
    @@ -594,9 +594,9 @@ <h5 class="modal-title">
                 </div>
               </div>
               <div class="modal-footer" v-if="fileSelect.selectDir && !fileSelect.previewRegex">
    -            <span v-html="fileSelect.selectShare ? '转存:' : '保存到:'"></span>
    +            <span>{{ fileSelect.selectShare ? '转存:' : '保存到:' }}</span>
                 <button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">当前文件夹</button>
    -            <button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">当前文件夹<span class="badge badge-light" v-if="fileSelect.index<this.formData.tasklist.length" v-html="'/'+formData.tasklist[fileSelect.index].taskname"></span></button>
    +            <button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">当前文件夹<span class="badge badge-light" v-if="fileSelect.index<this.formData.tasklist.length">/{{ formData.tasklist[fileSelect.index].taskname }}</span></button>
               </div>
             </div>
           </div>
    

Vulnerability mechanics

Root cause

"The template renders user-controlled push_config key names using Vue.js's v-html directive without escaping, enabling stored cross-site scripting."

Attack vector

An authenticated attacker sends a POST request to the /update endpoint with a crafted push_config key containing HTML or JavaScript payloads (e.g., `<img src=x onerror=alert(1)>`). The payload is persisted to disk and, when any authenticated user visits the System Configuration page, the `v-html="key"` directive in `app/templates/index.html` [patch_id=424515] renders the key name as raw HTML without sanitization [CWE-79]. This allows the attacker to exfiltrate session cookies or perform arbitrary actions in the context of the victim's session.

Affected code

The vulnerable code is in `app/templates/index.html` [patch_id=424515], specifically the line `<span class="input-group-text" v-html="key"></span>` which renders push_config key names. Multiple other `v-html` bindings throughout the same file (for plugin names, task names, error messages, and log content) are also replaced in the patch, indicating a systemic use of unsafe HTML rendering for user-influenced data.

What the fix does

The patch replaces all `v-html` bindings with Vue.js's text interpolation `{{ }}` across `app/templates/index.html` [patch_id=424515]. The `v-html` directive renders content as raw HTML, while `{{ }}` escapes the value and inserts it as a text node, preventing script execution. The most critical change is on the push_config key display (`<span class="input-group-text" v-html="key">` → `<span class="input-group-text">{{ key }}</span>`), which directly closes the stored XSS vector. Additional `v-html` instances for plugin names, task names, and other user-influenced fields are similarly hardened.

Preconditions

  • authAttacker must be authenticated to the Quark Drive application
  • networkAttacker must be able to send POST requests to the /update endpoint
  • inputAttacker must supply a push_config key name containing HTML/JavaScript payload

Generated on May 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.